import { DataflowAnalyzer } from ".."; import { ExecutionLogSlicer } from "../log-slicer"; import { TestCell } from "./testcell"; function makeLog(lines: string[]) { const cells = lines.map((text, i) => new TestCell(text, i + 1)); const logSlicer = new ExecutionLogSlicer(new DataflowAnalyzer()); cells.forEach(cell => logSlicer.logExecution(cell)); return logSlicer; } describe("log-slicer", () => { it("does the basics", () => { const lines = ["x=5", "y=6", "print(x+y)"]; const logSlicer = makeLog(lines); const lastCell = logSlicer.cellExecutions[logSlicer.cellExecutions.length - 1].cell; const slices = logSlicer.sliceAllExecutions(lastCell.persistentId); expect(slices).toBeDefined(); expect(slices).toHaveLength(1); const slice = slices[0]; expect(slice).toBeDefined(); expect(slice.cellSlices).toBeDefined(); expect(slice.cellSlices).toHaveLength(3); slice.cellSlices.forEach((cs, i) => { expect(cs).toBeDefined(); expect(cs.textSliceLines).toEqual(lines[i]); expect(cs.textSlice).toEqual(lines[i]); }); }); it("does jim's demo", () => { const lines = [ /*[1]*/ "import pandas as pd", /*[2]*/ "Cars = {'Brand': ['Honda Civic','Toyota Corolla','Ford Focus','Audi A4'], 'Price': [22000,25000,27000,35000]}\n" + "df = pd.DataFrame(Cars,columns= ['Brand', 'Price'])", /*[3]*/ "def check(df, size=11):\n" + " print(df)", /*[4]*/ "print(df)", /*[5]*/ "x = df['Brand'].values" ]; const logSlicer = makeLog(lines); const lastCell = logSlicer.cellExecutions[logSlicer.cellExecutions.length - 1].cell; const slice = logSlicer.sliceLatestExecution(lastCell.persistentId); expect(slice).toBeDefined(); expect(slice.cellSlices).toBeDefined(); [1, 2, 5].forEach((c, i) => expect(slice.cellSlices[i].textSlice).toEqual(lines[c - 1]) ); const cellCounts = slice.cellSlices.map(cell => cell.cell.executionCount); [3, 4].forEach(c => expect(cellCounts).not.toContainEqual(c)); }); it("works with a selected cell that has been executed twice", () => { const logSlicer = new ExecutionLogSlicer(new DataflowAnalyzer()); const lines = [ ["0", "a = 1"], ["1", "b = 2"], ["2", "b"], ["1", "b = a + 1"], ["2", "b"] ]; const cells = lines.map( ([pid, text], i) => new TestCell(text, i + 1, undefined, pid) ); cells.forEach(cell => logSlicer.logExecution(cell)); const slice = logSlicer.sliceLatestExecution("2"); expect(slice.cellSlices).toHaveLength(3); const sliceText = slice.cellSlices.map(c => c.textSlice); expect(sliceText).toContain(lines[0][1]); expect(sliceText).toContain(lines[3][1]); expect(sliceText).toContain(lines[4][1]); }); describe("getDependentCells", () => { it("handles simple in-order", () => { const lines = ["x = 3", "y = x+1"]; const logSlicer = makeLog(lines); const deps = logSlicer.getDependentCells( logSlicer.cellExecutions[0].cell.executionEventId ); expect(deps).toBeDefined(); expect(deps).toHaveLength(1); expect(deps[0].text).toBe(lines[1]); }); it("handles variable redefinition", () => { const lines = ["x = 3", "y = x+1", "x = 4", "y = x*2"]; const logSlicer = makeLog(lines); const deps = logSlicer.getDependentCells( logSlicer.cellExecutions[0].cell.executionEventId ); expect(deps).toBeDefined(); expect(deps).toHaveLength(1); expect(deps[0].text).toBe(lines[1]); const deps2 = logSlicer.getDependentCells( logSlicer.cellExecutions[2].cell.executionEventId ); expect(deps2).toBeDefined(); expect(deps2).toHaveLength(1); expect(deps2[0].text).toBe(lines[3]); }); it("handles no deps", () => { const lines = ["x = 3\nprint(x)", "y = 2\nprint(y)"]; const logSlicer = makeLog(lines); const deps = logSlicer.getDependentCells( logSlicer.cellExecutions[0].cell.executionEventId ); expect(deps).toBeDefined(); expect(deps).toHaveLength(0); }); it("works transitively", () => { const lines = ["x = 3", "y = x+1", "z = y-1"]; const logSlicer = makeLog(lines); const deps = logSlicer.getDependentCells( logSlicer.cellExecutions[0].cell.executionEventId ); expect(deps).toBeDefined(); expect(deps).toHaveLength(2); const deplines = deps.map(d => d.text); expect(deplines).toContain(lines[1]); expect(deplines).toContain(lines[2]); }); it("includes all defs within cells", () => { const lines = ["x = 3\nq = 2", "y = x+1", "z = q-1"]; const logSlicer = makeLog(lines); const deps = logSlicer.getDependentCells( logSlicer.cellExecutions[0].cell.executionEventId ); expect(deps).toBeDefined(); expect(deps).toHaveLength(2); const deplines = deps.map(d => d.text); expect(deplines).toContain(lines[1]); expect(deplines).toContain(lines[2]); }); it("handles cell re-execution", () => { const lines = [ ["0", "x = 2\nprint(x)"], ["1", "y = x+1\nprint(y)"], ["2", "q = 2"], ["0", "x = 20\nprint(x)"] ]; const cells = lines.map( ([pid, text], i) => new TestCell(text, i + 1, undefined, pid) ); const logSlicer = new ExecutionLogSlicer(new DataflowAnalyzer()); cells.forEach(cell => logSlicer.logExecution(cell)); const rerunFirst = logSlicer.cellExecutions[3].cell.executionEventId; const deps = logSlicer.getDependentCells(rerunFirst); expect(deps).toBeDefined(); expect(deps).toHaveLength(1); expect(deps[0].text).toContain(lines[1][1]); }); it("handles cell re-execution no-op", () => { const lines = [ ["0", "x = 2\nprint(x)"], ["1", "y = 3\nprint(y)"], ["2", "q = 2"], ["0", "x = 20\nprint(x)"] ]; const cells = lines.map( ([pid, text], i) => new TestCell(text, i + 1, undefined, pid) ); const logSlicer = new ExecutionLogSlicer(new DataflowAnalyzer()); cells.forEach(cell => logSlicer.logExecution(cell)); const deps = logSlicer.getDependentCells( logSlicer.cellExecutions[3].cell.executionEventId ); expect(deps).toBeDefined(); expect(deps).toHaveLength(0); }); it("return result in topo order", () => { const lines = [ ["0", "x = 1"], ["0", "y = 2*x"], ["0", "z = x*y"], ["0", "x = 2"], ["1", "y = x*2"], ["2", "z = y*x"], ["0", "x = 3"] ]; const cells = lines.map( ([pid, text], i) => new TestCell(text, i + 1, undefined, pid) ); const logSlicer = new ExecutionLogSlicer(new DataflowAnalyzer()); cells.forEach(cell => logSlicer.logExecution(cell)); const lastEvent = logSlicer.cellExecutions[logSlicer.cellExecutions.length - 1].cell .executionEventId; const deps = logSlicer.getDependentCells(lastEvent); expect(deps).toBeDefined(); expect(deps).toHaveLength(2); expect(deps[0].text).toBe("y = x*2"); expect(deps[1].text).toBe("z = y*x"); }); it("can be called multiple times", () => { const lines = [["0", "x = 1"], ["1", "y = 2*x"], ["2", "z = x*y"]]; const cells = lines.map( ([pid, text], i) => new TestCell(text, i + 1, undefined, pid) ); const logSlicer = new ExecutionLogSlicer(new DataflowAnalyzer()); cells.forEach(cell => logSlicer.logExecution(cell)); const deps = logSlicer.getDependentCells( logSlicer.cellExecutions[0].cell.executionEventId ); expect(deps).toBeDefined(); expect(deps).toHaveLength(2); expect(deps[0].text).toBe("y = 2*x"); expect(deps[1].text).toBe("z = x*y"); const edits = [ ["0", "x = 2"], ["1", "y = x*2"], ["2", "z = y*x"], ["0", "x = 3"] ]; const cellEdits = edits.map( ([pid, text], i) => new TestCell(text, i + 1, undefined, pid) ); cellEdits.forEach(cell => logSlicer.logExecution(cell)); const lastEvent = logSlicer.cellExecutions[logSlicer.cellExecutions.length - 1].cell .executionEventId; const deps2 = logSlicer.getDependentCells(lastEvent); expect(deps2).toBeDefined(); expect(deps2).toHaveLength(2); expect(deps2[0].text).toBe("y = x*2"); expect(deps2[1].text).toBe("z = y*x"); }); it("handles api calls", () => { const lines = [ [ "0", "from matplotlib.pyplot import scatter\nfrom sklearn.cluster import KMeans\nfrom sklearn import datasets" ], [ "1", "data = datasets.load_iris().data[:,2:4]\npetal_length, petal_width = data[:,1], data[:,0]" ], ["2", "k=3"], ["3", "clusters = KMeans(n_clusters=k).fit(data).labels_"], ["4", "scatter(petal_length, petal_width, c=clusters)"], ["2", "k=4"] ]; const cells = lines.map( ([pid, text], i) => new TestCell(text, i + 1, undefined, pid) ); const logSlicer = new ExecutionLogSlicer(new DataflowAnalyzer()); cells.forEach(cell => logSlicer.logExecution(cell)); const lastEvent = logSlicer.cellExecutions[logSlicer.cellExecutions.length - 1].cell .executionEventId; const deps = logSlicer.getDependentCells(lastEvent); expect(deps).toBeDefined(); expect(deps).toHaveLength(2); const sliceText = deps.map(c => c.text); expect(sliceText).toContain(lines[3][1]); expect(sliceText).toContain(lines[4][1]); }); }); });