-
Notifications
You must be signed in to change notification settings - Fork 5.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
8e958d3
commit 7a0670a
Showing
6 changed files
with
167 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
// Copyright 2018 the Deno authors. All rights reserved. MIT license. | ||
import * as fbs from "gen/msg_generated"; | ||
|
||
export interface TraceInfo { | ||
sync: boolean; // is synchronous call | ||
name: string; // name of operation | ||
} | ||
|
||
interface TraceStackNode { | ||
list: TraceInfo[]; | ||
prev: TraceStackNode | null; | ||
} | ||
|
||
let current: TraceStackNode | null = null; | ||
|
||
// Push a new list to trace stack | ||
function pushStack(): void { | ||
if (current === null) { | ||
current = { list: [], prev: null }; | ||
} else { | ||
const newStack = { list: [], prev: current }; | ||
current = newStack; | ||
} | ||
} | ||
|
||
// Pop from trace stack and (if possible) concat to parent trace stack node | ||
function popStack(): TraceInfo[] { | ||
if (current === null) { | ||
throw new Error("trace list stack should not be empty"); | ||
} | ||
const resultList = current!.list; | ||
if (!!current!.prev) { | ||
const prev = current!.prev!; | ||
// concat inner results to outer stack | ||
prev.list = prev.list.concat(resultList); | ||
current = prev; | ||
} else { | ||
current = null; | ||
} | ||
return resultList; | ||
} | ||
|
||
// Push to trace stack if we are tracing | ||
export function maybePushTrace(op: fbs.Any, sync: boolean): void { | ||
if (current === null) { | ||
return; // no trace requested | ||
} | ||
// Freeze the object, avoid tampering | ||
current!.list.push( | ||
Object.freeze({ | ||
sync, | ||
name: fbs.Any[op] // convert to enum names | ||
}) | ||
); | ||
} | ||
|
||
/** | ||
* Trace operations executed inside a given function or promise. | ||
* Notice: To capture every operation in asynchronous deno.* calls, | ||
* you might want to put them in functions instead of directly invoking. | ||
* | ||
* import { trace, mkdir } from "deno"; | ||
* | ||
* const ops = await trace(async () => { | ||
* await mkdir("my_dir"); | ||
* }); | ||
* // ops becomes [{ sync: false, name: "Mkdir" }] | ||
*/ | ||
export async function trace( | ||
// tslint:disable-next-line:no-any | ||
fnOrPromise: Function | Promise<any> | ||
): Promise<TraceInfo[]> { | ||
pushStack(); | ||
if (typeof fnOrPromise === "function") { | ||
await fnOrPromise(); | ||
} else { | ||
await fnOrPromise; | ||
} | ||
return popStack(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
import { testPerm, assertEqual } from "./test_util.ts"; | ||
import * as deno from "deno"; | ||
|
||
testPerm({ write: true }, async function traceFunctionSuccess() { | ||
const op = await deno.trace(async () => { | ||
const enc = new TextEncoder(); | ||
const data = enc.encode("Hello"); | ||
// Mixing sync and async calls | ||
const filename = deno.makeTempDirSync() + "/test.txt"; | ||
await deno.writeFile(filename, data, 0o666); | ||
await deno.removeSync(filename); | ||
}); | ||
assertEqual(op.length, 3); | ||
assertEqual(op[0], { sync: true, name: "MakeTempDir" }); | ||
assertEqual(op[1], { sync: false, name: "WriteFile" }); | ||
assertEqual(op[2], { sync: true, name: "Remove" }); | ||
}); | ||
|
||
testPerm({ write: true }, async function tracePromiseSuccess() { | ||
// Ensure we don't miss any send actions | ||
// (new Promise(fn), fn runs synchronously) | ||
const asyncFunction = async () => { | ||
const enc = new TextEncoder(); | ||
const data = enc.encode("Hello"); | ||
// Mixing sync and async calls | ||
const filename = deno.makeTempDirSync() + "/test.txt"; | ||
await deno.writeFile(filename, data, 0o666); | ||
await deno.removeSync(filename); | ||
}; | ||
const promise = Promise.resolve().then(asyncFunction); | ||
const op = await deno.trace(promise); | ||
assertEqual(op.length, 3); | ||
assertEqual(op[0], { sync: true, name: "MakeTempDir" }); | ||
assertEqual(op[1], { sync: false, name: "WriteFile" }); | ||
assertEqual(op[2], { sync: true, name: "Remove" }); | ||
}); | ||
|
||
testPerm({ write: true }, async function traceRepeatSuccess() { | ||
const op1 = await deno.trace(async () => await deno.makeTempDir()); | ||
assertEqual(op1.length, 1); | ||
assertEqual(op1[0], { sync: false, name: "MakeTempDir" }); | ||
const op2 = await deno.trace(async () => await deno.statSync(".")); | ||
assertEqual(op2.length, 1); | ||
assertEqual(op2[0], { sync: true, name: "Stat" }); | ||
}); | ||
|
||
testPerm({ write: true }, async function traceIdempotence() { | ||
let op1, op2, op3; | ||
op1 = await deno.trace(async () => { | ||
const filename = (await deno.makeTempDir()) + "/test.txt"; | ||
op2 = await deno.trace(async () => { | ||
const enc = new TextEncoder(); | ||
const data = enc.encode("Hello"); | ||
deno.writeFileSync(filename, data, 0o666); | ||
op3 = await deno.trace(async () => { | ||
await deno.remove(filename); | ||
}); | ||
await deno.makeTempDir(); | ||
}); | ||
}); | ||
|
||
// Flatten the calls | ||
assertEqual(op1.length, 4); | ||
assertEqual(op1[0], { sync: false, name: "MakeTempDir" }); | ||
assertEqual(op1[1], { sync: true, name: "WriteFile" }); | ||
assertEqual(op1[2], { sync: false, name: "Remove" }); | ||
assertEqual(op1[3], { sync: false, name: "MakeTempDir" }); | ||
|
||
assertEqual(op2.length, 3); | ||
assertEqual(op2[0], { sync: true, name: "WriteFile" }); | ||
assertEqual(op2[1], { sync: false, name: "Remove" }); | ||
assertEqual(op2[2], { sync: false, name: "MakeTempDir" }); | ||
|
||
assertEqual(op3.length, 1); | ||
assertEqual(op3[0], { sync: false, name: "Remove" }); | ||
|
||
// Expect top-level repeat still works after all the nestings | ||
const op4 = await deno.trace(async () => await deno.statSync(".")); | ||
assertEqual(op4.length, 1); | ||
assertEqual(op4[0], { sync: true, name: "Stat" }); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters