diff --git a/src/core/steps/all/static-slicing/10-reconstruct.ts b/src/core/steps/all/static-slicing/10-reconstruct.ts index 187c2c702a..90eb700b2b 100644 --- a/src/core/steps/all/static-slicing/10-reconstruct.ts +++ b/src/core/steps/all/static-slicing/10-reconstruct.ts @@ -15,7 +15,11 @@ function processor(results: { normalize?: NormalizedAst, slice?: SliceResult }, return reconstructToCode(results.normalize as NormalizedAst, (results.slice as SliceResult).result, input.autoSelectIf) } -export const NAIVE_RECONSTRUCT = { +function diceProcessor(results: { normalize?: NormalizedAst, dice?: SliceResult }, input: Partial) { + return reconstructToCode(results.normalize as NormalizedAst, (results.dice as SliceResult).result, input.autoSelectIf) +} + +export const NAIVE_RECONSTRUCT_SLICED = { name: 'reconstruct', humanReadableName: 'static code reconstruction', description: 'Reconstruct R code from the static slice', @@ -27,3 +31,17 @@ export const NAIVE_RECONSTRUCT = { dependencies: [ 'slice' ], requiredInput: undefined as unknown as ReconstructRequiredInput } as const satisfies DeepReadonly> + +export const NAIVE_RECONSTRUCT_DICED = { + name: 'reconstruct', + humanReadableName: 'static code reconstruction', + description: 'Reconstruct R code from the static dice', + + processor: diceProcessor, + executed: PipelineStepStage.OncePerRequest, + printer: { + [StepOutputFormat.Internal]: internalPrinter + }, + dependencies: [ 'dice' ], + requiredInput: undefined as unknown as ReconstructRequiredInput +} as const satisfies DeepReadonly> diff --git a/src/core/steps/all/static-slicing/20-dicing.ts b/src/core/steps/all/static-slicing/20-dicing.ts new file mode 100644 index 0000000000..aa12a18a88 --- /dev/null +++ b/src/core/steps/all/static-slicing/20-dicing.ts @@ -0,0 +1,35 @@ +import type { DeepReadonly } from 'ts-essentials' +import type { DataflowInformation } from '../../../../dataflow/info' +import type { NormalizedAst } from '../../../../r-bridge/lang-4.x/ast/model/processing/decorate' +import type { DicingCriterion } from '../../../../slicing/criterion/parse' +import { PipelineStepStage } from '../../pipeline-step' +import type { IPipelineStep } from '../../pipeline-step' +import { internalPrinter, StepOutputFormat } from '../../../print/print' +import { staticDicing } from '../../../../slicing/static/dicer' + +export interface DiceRequiredInput { + /** The dicing criterion is only of interest if you actually want to Dice the R code */ + readonly startingCriterion: DicingCriterion, + + readonly endCriterion: DicingCriterion, + + /** How many re-visits of the same node are ok? */ + readonly threshold?: number +} + +function processor(results: { dataflow?: DataflowInformation, normalize?: NormalizedAst }, input: Partial) { + return staticDicing((results.dataflow as DataflowInformation).graph, results.normalize as NormalizedAst, input.endCriterion as DicingCriterion, input.startingCriterion as DicingCriterion, input.threshold) +} + +export const STATIC_DICE = { + name: 'dice', + humanReadableName: 'static dice', + description: 'Calculate the actual static dice from the dataflow graph and the given slicing criteria', + processor, + executed: PipelineStepStage.OncePerRequest, + printer: { + [StepOutputFormat.Internal]: internalPrinter + }, + dependencies: [ 'dataflow' ], + requiredInput: undefined as unknown as DiceRequiredInput +} as const satisfies DeepReadonly> \ No newline at end of file diff --git a/src/core/steps/pipeline/default-pipelines.ts b/src/core/steps/pipeline/default-pipelines.ts index 10a3580710..f988810994 100644 --- a/src/core/steps/pipeline/default-pipelines.ts +++ b/src/core/steps/pipeline/default-pipelines.ts @@ -6,11 +6,14 @@ import { PARSE_WITH_R_SHELL_STEP } from '../all/core/00-parse' import { NORMALIZE } from '../all/core/10-normalize' import { STATIC_DATAFLOW } from '../all/core/20-dataflow' import { STATIC_SLICE } from '../all/static-slicing/00-slice' -import { NAIVE_RECONSTRUCT } from '../all/static-slicing/10-reconstruct' +import { NAIVE_RECONSTRUCT_DICED, NAIVE_RECONSTRUCT_SLICED } from '../all/static-slicing/10-reconstruct' +import { STATIC_DICE } from '../all/static-slicing/20-dicing' -export const DEFAULT_SLICING_PIPELINE = createPipeline(PARSE_WITH_R_SHELL_STEP, NORMALIZE, STATIC_DATAFLOW, STATIC_SLICE, NAIVE_RECONSTRUCT) +export const DEFAULT_SLICING_PIPELINE = createPipeline(PARSE_WITH_R_SHELL_STEP, NORMALIZE, STATIC_DATAFLOW, STATIC_SLICE, NAIVE_RECONSTRUCT_SLICED) export const DEFAULT_SLICE_AND_RECONSTRUCT_PIPELINE = DEFAULT_SLICING_PIPELINE +export const DEFAULT_DICING_PIPELINE = createPipeline(PARSE_WITH_R_SHELL_STEP, NORMALIZE, STATIC_DATAFLOW, STATIC_DICE, NAIVE_RECONSTRUCT_DICED) + export const DEFAULT_DATAFLOW_PIPELINE = createPipeline(PARSE_WITH_R_SHELL_STEP, NORMALIZE, STATIC_DATAFLOW) export const DEFAULT_NORMALIZE_PIPELINE = createPipeline(PARSE_WITH_R_SHELL_STEP, NORMALIZE) diff --git a/src/slicing/criterion/parse.ts b/src/slicing/criterion/parse.ts index 460dba0658..de73fa7561 100644 --- a/src/slicing/criterion/parse.ts +++ b/src/slicing/criterion/parse.ts @@ -99,3 +99,8 @@ export type DecodedCriteria = ReadonlyArray export function convertAllSlicingCriteriaToIds(criteria: SlicingCriteria, decorated: AstIdMap): DecodedCriteria { return criteria.map(l => ({ criterion: l, id: slicingCriterionToId(l, decorated) })) } + +export interface DicingCriterion { + readonly type: 'union' | 'intersection' | 'symetrical difference'; + readonly criteria: readonly SingleSlicingCriterion[]; +} diff --git a/src/slicing/static/dicer.ts b/src/slicing/static/dicer.ts new file mode 100644 index 0000000000..abd0a7dd82 --- /dev/null +++ b/src/slicing/static/dicer.ts @@ -0,0 +1,193 @@ +import { initializeCleanEnvironments } from '../../dataflow/environments/environment' +import { edgeIncludesType, EdgeType, shouldTraverseEdge, TraverseEdge } from '../../dataflow/graph/edge' +import type { DataflowGraph } from '../../dataflow/graph/graph' +import { VertexType } from '../../dataflow/graph/vertex' +import type { NormalizedAst } from '../../r-bridge/lang-4.x/ast/model/processing/decorate' +import type { NodeId } from '../../r-bridge/lang-4.x/ast/model/processing/node-id' +import { guard } from '../../util/assert' +import { jsonReplacer } from '../../util/json' +import { expensiveTrace } from '../../util/log' +import { convertAllSlicingCriteriaToIds, type DicingCriterion, type SlicingCriteria } from '../criterion/parse' +import { envFingerprint } from './fingerprint' +import { handleReturns, sliceForCall } from './slice-call' +import type { SliceResult } from './slicer-types' +import { slicerLogger, staticSlicing } from './static-slicer' +import { VisitingQueue } from './visiting-queue' + + + +export function staticDicing(graph: DataflowGraph, ast: NormalizedAst, endCriteria: DicingCriterion, startCriteria: DicingCriterion, threshold = 75): Readonly { + let backwardsSlice: Readonly | undefined = undefined + let forwardSlice: Readonly | undefined = undefined + + switch(endCriteria.type) { + case 'union': { + backwardsSlice = staticSlicing(graph, ast, endCriteria.criteria as SlicingCriteria, threshold) + break + } + + case 'intersection': { + const slices: Readonly[] = [] + for(const criteria of endCriteria.criteria) { + slices.push(staticSlicing(graph, ast, [criteria], threshold)) + } + backwardsSlice = slices.reduceRight((previousValue, currentValue, _currentIndex, _array) => { + return { + timesHitThreshold: previousValue.timesHitThreshold + currentValue.timesHitThreshold, + result: new Set([...previousValue.result].filter(i => currentValue.result.has(i))), + decodedCriteria: previousValue.decodedCriteria.concat(currentValue.decodedCriteria) + } + }) + break + } + + case 'symetrical difference': { + backwardsSlice = staticSlicing(graph, ast, endCriteria.criteria as SlicingCriteria, threshold) + break + } + } + + switch(startCriteria.type) { + case 'union': { + forwardSlice = forwardSlicing(graph, ast, startCriteria.criteria as SlicingCriteria, threshold) + break + } + + case 'intersection': { + const slices: Readonly[] = [] + for(const criteria of startCriteria.criteria) { + const partialSlice = forwardSlicing(graph, ast, [criteria], threshold) + slices.push(partialSlice) + } + forwardSlice = slices.reduceRight((previousValue, currentValue, _currentIndex, _array) => { + return { + timesHitThreshold: previousValue.timesHitThreshold + currentValue.timesHitThreshold, + result: new Set([...previousValue.result].filter(i => currentValue.result.has(i))), + decodedCriteria: previousValue.decodedCriteria.concat(currentValue.decodedCriteria) + } + }) + break + } + + case 'symetrical difference': { + const union = forwardSlicing(graph, ast, startCriteria.criteria as SlicingCriteria, threshold) + + const slices: Readonly[] = [] + for(const criteria of startCriteria.criteria) { + const partialSlice = forwardSlicing(graph, ast, [criteria], threshold) + slices.push(partialSlice) + } + const intersection = slices.reduceRight((previousValue, currentValue, _currentIndex, _array) => { + return { + timesHitThreshold: previousValue.timesHitThreshold + currentValue.timesHitThreshold, + result: new Set([...previousValue.result].filter(i => currentValue.result.has(i))), + decodedCriteria: previousValue.decodedCriteria.concat(currentValue.decodedCriteria) + } + }) + + forwardSlice = { + timesHitThreshold: union.timesHitThreshold - intersection.timesHitThreshold, + result: new Set([...union.result].filter(i => !intersection.result.has(i))), + decodedCriteria: union.decodedCriteria.filter(i => !intersection.decodedCriteria.includes(i)) + } + console.log('union: %s\nintersection: %s\ndifference: %s', JSON.stringify(union.result, jsonReplacer), JSON.stringify(intersection.result, jsonReplacer), JSON.stringify(forwardSlice.result, jsonReplacer)) + break + } + } + + const diceResult = new Set([...backwardsSlice.result].filter(i => forwardSlice.result.has(i))) + //console.log(diceResult) + const dicingResult = { timesHitThreshold: backwardsSlice.timesHitThreshold + forwardSlice.timesHitThreshold, result: diceResult, decodedCriteria: backwardsSlice.decodedCriteria.concat(forwardSlice.decodedCriteria) } + return dicingResult +} + +function forwardSlicing(graph: DataflowGraph, ast: NormalizedAst, criteria: SlicingCriteria, threshold = 75): Readonly { + const idMap = ast.idMap + + guard(criteria.length > 0, 'must have at least one seed id to calculate slice') + const decodedCriteria = convertAllSlicingCriteriaToIds(criteria, idMap) + expensiveTrace(slicerLogger, + () => `calculating slice for ${decodedCriteria.length} seed criteria: ${decodedCriteria.map(s => JSON.stringify(s)).join(', ')}` + ) + + const queue = new VisitingQueue(threshold) + + let minDepth = Number.MAX_SAFE_INTEGER + const sliceSeedIds = new Set() + // every node ships the call environment which registers the calling environment + { + const emptyEnv = initializeCleanEnvironments() + const basePrint = envFingerprint(emptyEnv) + for(const { id: startId } of decodedCriteria) { + queue.add(startId, emptyEnv, basePrint, false) + // retrieve the minimum depth of all nodes to only add control dependencies if they are "part" of the current execution + minDepth = Math.min(minDepth, idMap.get(startId)?.info.depth ?? minDepth) + sliceSeedIds.add(startId) + } + } + + const visitedIds = [] + while(queue.nonEmpty()) { + const current = queue.next() + const { baseEnvironment, id, onlyForSideEffects } = current + const baseEnvFingerprint = envFingerprint(baseEnvironment) + + //This is for debug only + visitedIds.push(id) + + const currentInfo = graph.get(id, true) + if(currentInfo === undefined) { + slicerLogger.warn(`id: ${id} must be in graph but can not be found, keep in slice to be sure`) + continue + } + + const [currentVertex, currentEdges] = currentInfo + const ingoingEdges = graph.ingoingEdges(id) + if(ingoingEdges === undefined) { + continue + } + + // we only add control dependencies iff 1) we are in different function call or 2) they have, at least, the same depth as the slicing seed + if(currentVertex.controlDependencies && currentVertex.controlDependencies.length > 0) { + const topLevel = graph.isRoot(id) || sliceSeedIds.has(id) + for(const cd of currentVertex.controlDependencies.filter(({ id }) => !queue.hasId(id))) { + if(!topLevel || (idMap.get(cd.id)?.info.depth ?? 0) <= minDepth) { + queue.add(cd.id, baseEnvironment, baseEnvFingerprint, false) + } + } + } + + //add function definition (subflow) + if(!onlyForSideEffects) { + if(currentVertex.tag === VertexType.FunctionCall && !currentVertex.onlyBuiltin) { + sliceForCall(current, currentVertex, graph, queue) + } + + const ret = handleReturns(queue, currentEdges, baseEnvFingerprint, baseEnvironment) + if(ret) { + continue + } + } + + for(const [target, { types }] of ingoingEdges) { + if(edgeIncludesType(types, EdgeType.NonStandardEvaluation)) { + continue + } + const t = shouldTraverseEdge(types) + if(t === TraverseEdge.Always) { + queue.add(target, baseEnvironment, baseEnvFingerprint, false) + } else if(t === TraverseEdge.DefinedByOnCall) { + const n = queue.potentialArguments.get(target) + if(n) { + queue.add(target, n.baseEnvironment, envFingerprint(n.baseEnvironment), n.onlyForSideEffects) + queue.potentialArguments.delete(target) + } + } else if(t === TraverseEdge.SideEffect) { + queue.add(target, baseEnvironment, baseEnvFingerprint, true) + } + } + } + //console.log('\n\nvisitedIds: %s\n\n', visitedIds) + + return { ...queue.status(), decodedCriteria } +} \ No newline at end of file diff --git a/test/functionality/_helper/shell.ts b/test/functionality/_helper/shell.ts index 779efa77e2..4054f4a3be 100644 --- a/test/functionality/_helper/shell.ts +++ b/test/functionality/_helper/shell.ts @@ -4,7 +4,7 @@ import { assert } from 'chai' import { testRequiresRVersion } from './version' import type { MergeableRecord } from '../../../src/util/objects' import { deepMergeObject } from '../../../src/util/objects' -import { NAIVE_RECONSTRUCT } from '../../../src/core/steps/all/static-slicing/10-reconstruct' +import { NAIVE_RECONSTRUCT_SLICED } from '../../../src/core/steps/all/static-slicing/10-reconstruct' import { guard } from '../../../src/util/assert' import { PipelineExecutor } from '../../../src/core/pipeline-executor' import type { TestLabel } from './label' @@ -23,6 +23,7 @@ import { } from '../../../src/r-bridge/lang-4.x/ast/model/processing/decorate' import { DEFAULT_DATAFLOW_PIPELINE, + DEFAULT_DICING_PIPELINE, DEFAULT_NORMALIZE_PIPELINE, DEFAULT_SLICE_AND_RECONSTRUCT_PIPELINE } from '../../../src/core/steps/pipeline/default-pipelines' import type { RExpressionList } from '../../../src/r-bridge/lang-4.x/ast/model/nodes/r-expression-list' @@ -31,10 +32,12 @@ import { diffOfDataflowGraphs } from '../../../src/dataflow/graph/diff' import type { NodeId } from '../../../src/r-bridge/lang-4.x/ast/model/processing/node-id' import type { DataflowGraph } from '../../../src/dataflow/graph/graph' import { diffGraphsToMermaidUrl, graphToMermaidUrl } from '../../../src/util/mermaid/dfg' -import type { SlicingCriteria } from '../../../src/slicing/criterion/parse' +import type { DicingCriterion, SlicingCriteria } from '../../../src/slicing/criterion/parse' import { normalizedAstToMermaidUrl } from '../../../src/util/mermaid/ast' import type { AutoSelectPredicate } from '../../../src/reconstruct/auto-select/auto-select-defaults' import { resolveDataflowGraph } from './resolve-graph' +import { jsonReplacer } from '../../../src/util/json' +import { setEquals } from '../../../src/util/set' export const testWithShell = (msg: string, fn: (shell: RShell, test: Mocha.Context) => void | Promise): Mocha.Test => { return it(msg, async function(): Promise { @@ -308,7 +311,7 @@ export function assertReconstructed(name: string | TestLabel, shell: RShell, inp request: requestFromInput(input), shell }).allRemainingSteps() - const reconstructed = NAIVE_RECONSTRUCT.processor({ + const reconstructed = NAIVE_RECONSTRUCT_SLICED.processor({ normalize: result.normalize, slice: { decodedCriteria: [], @@ -360,3 +363,85 @@ export function assertSliced( handleAssertOutput(name, shell, input, userConfig) return t } + + +//make it check the ids primarily and not the reconstruction +export function assertDicedIds( + name: string | TestLabel, + shell: RShell, + input: string, + startCriteria: DicingCriterion, + endCriteria: DicingCriterion, + expected: ReadonlySet, + userConfig?: Partial & { autoSelectIf?: AutoSelectPredicate }, + getId: IdGenerator = deterministicCountingIdGenerator(0) +): Mocha.Test { + const fullname = decorateLabelContext(name, ['slice']) + + const t = it(`${JSON.stringify(startCriteria)} to ${JSON.stringify(endCriteria)} ${fullname}`, async function() { + await ensureConfig(shell, this, userConfig) + + console.log('input: %s\ncriteria: %s, %s', requestFromInput(input), JSON.stringify(startCriteria), JSON.stringify(endCriteria)) + const result = await new PipelineExecutor(DEFAULT_DICING_PIPELINE,{ + getId, + request: requestFromInput(input), + shell, + startingCriterion: startCriteria, + endCriterion: endCriteria, + autoSelectIf: userConfig?.autoSelectIf + }).allRemainingSteps() + + try { + assert.isTrue( + setEquals(result.dice.result, expected), + `got: ${JSON.stringify(result.dice.result, jsonReplacer)}, vs. expected: ${JSON.stringify(expected, jsonReplacer)}, for input ${input} (slice for ${JSON.stringify(startCriteria)} to ${JSON.stringify(endCriteria)}`//: ${printIdMapping(result.slice.decodedCriteria.map(({ id }) => id), result.normalize.idMap)}), url: ${graphToMermaidUrl(result.dataflow.graph, true, result.slice.result)}` + ) + } catch(e) { + console.error(`got: ${JSON.stringify(result.dice.result, jsonReplacer)}, vs. expected: ${JSON.stringify(expected, jsonReplacer)}`) + console.error(normalizedAstToMermaidUrl(result.normalize.ast)) + throw e + } + }) + handleAssertOutput(name, shell, input, userConfig) + return t +} + +export function assertDiced( + name: string | TestLabel, + shell: RShell, + input: string, + startCriteria: DicingCriterion, + endCriteria: DicingCriterion, + expected: string, + userConfig?: Partial & { autoSelectIf?: AutoSelectPredicate }, + getId: IdGenerator = deterministicCountingIdGenerator(0) +): Mocha.Test { + const fullname = decorateLabelContext(name, ['slice']) + + const t = it(`${JSON.stringify(startCriteria)} to ${JSON.stringify(endCriteria)} ${fullname}`, async function() { + await ensureConfig(shell, this, userConfig) + + console.log('input: %s\ncriteria: %s, %s', requestFromInput(input), JSON.stringify(startCriteria), JSON.stringify(endCriteria)) + const result = await new PipelineExecutor(DEFAULT_DICING_PIPELINE,{ + getId, + request: requestFromInput(input), + shell, + startingCriterion: startCriteria, + endCriterion: endCriteria, + autoSelectIf: userConfig?.autoSelectIf + }).allRemainingSteps() + + try { + assert.strictEqual( + result.reconstruct.code, expected, + `got: ${result.reconstruct.code}, vs. expected: ${expected}, for input ${input} (slice for ${JSON.stringify(startCriteria)} to ${JSON.stringify(endCriteria)}`//: ${printIdMapping(result.slice.decodedCriteria.map(({ id }) => id), result.normalize.idMap)}), url: ${graphToMermaidUrl(result.dataflow.graph, true, result.slice.result)}` + ) + } catch(e) { + console.error(`got:\n${result.reconstruct.code}\nvs. expected:\n${expected}`) + console.error(normalizedAstToMermaidUrl(result.normalize.ast)) + throw e + } + }) + handleAssertOutput(name, shell, input, userConfig) + return t +} diff --git a/test/functionality/pipelines/create/create-tests.ts b/test/functionality/pipelines/create/create-tests.ts index dd241b3ab7..892d60842f 100644 --- a/test/functionality/pipelines/create/create-tests.ts +++ b/test/functionality/pipelines/create/create-tests.ts @@ -5,7 +5,7 @@ import { allPermutations } from '../../../../src/util/arrays' import { NORMALIZE } from '../../../../src/core/steps/all/core/10-normalize' import { STATIC_DATAFLOW } from '../../../../src/core/steps/all/core/20-dataflow' import { STATIC_SLICE } from '../../../../src/core/steps/all/static-slicing/00-slice' -import { NAIVE_RECONSTRUCT } from '../../../../src/core/steps/all/static-slicing/10-reconstruct' +import { NAIVE_RECONSTRUCT_SLICED } from '../../../../src/core/steps/all/static-slicing/10-reconstruct' import { createPipeline } from '../../../../src/core/steps/pipeline/pipeline' describe('Create Pipeline (includes dependency checks)', () => { @@ -92,7 +92,7 @@ describe('Create Pipeline (includes dependency checks)', () => { NORMALIZE, STATIC_DATAFLOW, STATIC_SLICE, - NAIVE_RECONSTRUCT + NAIVE_RECONSTRUCT_SLICED ], ['parse', 'normalize', 'dataflow', 'slice', 'reconstruct'], 3) }) describe('with decorators', () => { @@ -157,7 +157,7 @@ describe('Create Pipeline (includes dependency checks)', () => { decorates: 'dataflow' }, STATIC_SLICE, - NAIVE_RECONSTRUCT + NAIVE_RECONSTRUCT_SLICED ], ['parse', 'normalize', 'dataflow', 'dataflow-decorator', 'slice', 'reconstruct'], 4) }) }) diff --git a/test/functionality/slicing/dicing/simple-tests.ts b/test/functionality/slicing/dicing/simple-tests.ts new file mode 100644 index 0000000000..db9ae888b3 --- /dev/null +++ b/test/functionality/slicing/dicing/simple-tests.ts @@ -0,0 +1,99 @@ +import { assertDiced, assertDicedIds, withShell } from '../../_helper/shell' +import type { DicingCriterion } from '../../../../src/slicing/criterion/parse' +import type { TestLabel } from '../../_helper/label' +import { label } from '../../_helper/label' +import type { NodeId } from '../../../../src/r-bridge/lang-4.x/ast/model/processing/node-id' + +describe.only('Simple', withShell(shell => { + describe('Checks for IDs', () => { + const testcases: { label: TestLabel, input: string, endCriterion: DicingCriterion, startCriterion: DicingCriterion, expected: ReadonlySet }[] + = [ + { label: label('Simple Example', ['assignment-functions', 'binary-operator']), input: 'a <- 3\nb <- 4\nc <- a + b', endCriterion: { type: 'union', criteria: ['3@c'] }, startCriterion: { type: 'union', criteria: ['1@a'] }, expected: new Set([6, 9, 10, 7, 0, 2]) as ReadonlySet } + ] + + for(const testcase of testcases) { + assertDicedIds(testcase.label, shell, testcase.input, testcase.startCriterion, testcase.endCriterion, testcase.expected) + } + }) + + describe('Base Dicing Cases', () => { + const testcases: { label: TestLabel, input: string, endCriterion: DicingCriterion, startCriterion: DicingCriterion, expected: string }[] + = [ + { label: label('Simple Example for a', ['assignment-functions', 'binary-operator']), input: 'a <- 3\nb <- 4\nc <- a + b', endCriterion: { type: 'union', criteria: ['3@c'] }, startCriterion: { type: 'union', criteria: ['1@a'] }, expected: 'a <- 3\nc <- a + b' }, + { label: label('Simple Example for b', ['assignment-functions', 'binary-operator']), input: 'a <- 3\nb <- 4\nc <- a + b', endCriterion: { type: 'union', criteria: ['3@c'] }, startCriterion: { type: 'union', criteria: ['2@b'] }, expected: 'b <- 4\nc <- b' }, + { label: label('Extended Example', ['assignment-functions', 'binary-operator']), input: 'a <- 3\nb <- 4\nc <- a + b\nd <- 5\ne <- d + c', endCriterion: { type: 'union', criteria: ['5@e'] }, startCriterion: { type: 'union', criteria: ['4@d'] }, expected: 'd <- 5\ne <- d + c' }, + { label: label('Multiple Start Points', ['assignment-functions', 'binary-operator']), input: 'a <- 3\nb <- 4\nc <- a + b\nd <- 5\ne <- d + c', endCriterion: { type: 'union', criteria: ['5@e'] }, startCriterion: { type: 'union', criteria: ['4@d', '3@c'] }, expected: 'c <- a + b\nd <- 5\ne <- d + c' }, + { label: label('Multiple End Points', ['assignment-functions', 'binary-operator']), input: 'a <- 3\nb <- 4\nc <- a + b\nd <- b + 5\ne <- d + c', endCriterion: { type: 'union', criteria: ['4@d', '3@c'] }, startCriterion: { type: 'union', criteria: ['2@b'] }, expected: 'b <- 4\nc <- b\nd <- b + 5' }, + ] + + for(const testcase of testcases) { + assertDiced(testcase.label, shell, testcase.input, testcase.startCriterion, testcase.endCriterion, testcase.expected) + } + }) + + describe('Dicing for Loops', () => { + const fibWhile = `x <- 1 +y <- 1 +i <- 0 +while (i < 10) { + h <- x + x <- x + y + y <- h + i <- i + 1 +} +cat(x)` + + assertDiced(label('Simple while', ['assignment-functions', 'binary-operator', 'while-loop']), shell, fibWhile, { type: 'union', criteria: ['2@y'] }, { type: 'union', criteria: ['10@x'] }, 'y <- 1\nwhile(i < 10) x <- y\nx') + assertDiced(label('Complex while', ['assignment-functions', 'binary-operator', 'while-loop']), shell, fibWhile, { type: 'union', criteria: ['2@y', '1@x'] }, { type: 'union', criteria: ['10@x'] }, 'x <- 1\ny <- 1\nwhile(i < 10) x <- x + y\nx') + assertDiced(label('End in while', ['assignment-functions', 'binary-operator', 'while-loop']), shell, fibWhile, { type: 'union', criteria: ['1@x'] }, { type: 'union', criteria: ['7@y'] }, 'x <- 1\nwhile(i < 10) {\n h <- x\n x <- x + y\n y <- h\n}') + assertDiced(label('Start in while', ['assignment-functions', 'binary-operator', 'while-loop']), shell, fibWhile, { type: 'union', criteria: ['6@x'] }, { type: 'union', criteria: ['10@x'] }, 'while(i < 10) x <- x + y\nx') + assertDiced(label('Dice in while', ['assignment-functions', 'binary-operator', 'while-loop']), shell, fibWhile, { type: 'union', criteria: ['5@x'] }, { type: 'union', criteria: ['7@y'] }, 'while(i < 10) {\n h <- x\n y <- h\n}') + + }) + + describe('Dicing with functions', () => { + const code = `x <- function(a, b) { + y <- 10 + y <- y + a + y * b +} +c <- 2 +d <- 10 +z <- x(d, c)` + + assertDiced(label('Simple function', ['assignment-functions', 'binary-operator', 'function-calls', 'function-definitions']), shell, code, { type: 'union', criteria: ['6@c'] }, { type: 'union', criteria: ['8@z'] }, 'x <- function(a, b) { y * b }\nc <- 2\nx(d,c)') + assertDiced(label('Start in function paramenter', ['assignment-functions', 'binary-operator', 'function-calls', 'function-definitions']), shell, code, { type: 'union', criteria: ['1@a'] }, { type: 'union', criteria: ['8@z'] }, 'x <- function(a, b) {\n y <- a\n y * b\n }\nx(d, c)') + assertDiced(label('Start in function', ['assignment-functions', 'binary-operator', 'function-calls', 'function-definitions']), shell, code, { type: 'union', criteria: ['3@a'] }, { type: 'union', criteria: ['8@z'] }, 'y <- a\ny * b') + assertDiced(label('Cuts out function parameter', ['assignment-functions', 'binary-operator', 'function-calls', 'function-definitions']), shell, code, { type: 'union', criteria: ['1@x'] }, { type: 'union', criteria: ['8@z'] }, 'x <- { y * b }\nx(d, c)') + }) + + describe('Intersection dicing', () => { + const fibWhile = `x <- 1 +y <- 1 +i <- 0 +while (i < 10) { + h <- x + x <- x + y + y <- h + i <- i + 1 +} +cat(x)` + + assertDicedIds(label('Simple Intersection ID Test', ['assignment-functions', 'binary-operator', 'while-loop']), shell, fibWhile, { type: 'intersection', criteria: ['1@x', '2@y'] }, { type: 'union', criteria: ['10@x'] }, new Set([33, 17, 20, 21, 31])) + }) + + describe('Symetrical Difference dicing', () => { + const fibWhile = `x <- 1 +y <- 1 +i <- 0 +while (i < 10) { + h <- x + x <- x + y + y <- h + i <- i + 1 +} +cat(x)` + + assertDicedIds(label('Simple Difference ID Test', ['assignment-functions', 'binary-operator', 'while-loop']), shell, fibWhile, { type: 'symetrical difference', criteria: ['1@x', '2@y'] }, { type: 'union', criteria: ['10@x'] }, new Set([0, 3, 5, 19, 2, 18])) + }) +})) \ No newline at end of file diff --git a/test/functionality/slicing/slicing.spec.ts b/test/functionality/slicing/slicing.spec.ts index 62096d9cfd..a276d12765 100644 --- a/test/functionality/slicing/slicing.spec.ts +++ b/test/functionality/slicing/slicing.spec.ts @@ -11,4 +11,7 @@ describe('Slicing', () => { describe('Slicing-Criterion', () => { requireAllTestsInFolder(path.join(__dirname, 'slicing-criterion')) }) + describe('Dicing', () => { + requireAllTestsInFolder(path.join(__dirname, 'dicing')) + }) })