Skip to content

Commit

Permalink
[Tests] Basic System Tests (#1176)
Browse files Browse the repository at this point in the history
* test(systest): implement structure for basic system tests (#1164)

* lint-fix(systest): fix any value (#1164)

* test(systests): run test for flowr server (#1164)

* lint(systests): lin fix (#1164)

* refactor(systests): use childprocess instead of zx (#1164)

* test-fix(systests): newline at end of file (#1164)

* ci(systets): add systests to ci (#1164)

* ci-fix(systest): extend test timeout (#1164)

* lint(systest): format config (#1164)

* refactor(test): defer math ast parsing in normalized ast fold

* refactor(system-tests): concat buffers in checking server output

* refactor(system-tests): sequentialize

* wip(debug): print stdout for server test

* wip(server-tests): only run server test for output

* refactor(server-test): kill the server thread with a sigkill

---------

Co-authored-by: Florian Sihler <[email protected]>
  • Loading branch information
gigalasr and EagleoutIce authored Nov 30, 2024
1 parent d9a91cb commit 0a57e68
Show file tree
Hide file tree
Showing 8 changed files with 146 additions and 9 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/qa.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,9 @@ jobs:
- name: "🧪 Run the Tests"
run: bash .github/workflows/scripts/run-flowr-command.sh "test-full -- --allowOnly=false"

- name: "⚙️ Run System Tests"
run: bash .github/workflows/scripts/run-flowr-command.sh "test:system -- --no-watch"

- name: "⬆️ Upload Coverage Reports to Codecov"
if: github.ref == 'refs/heads/main'
uses: codecov/codecov-action@v4
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ jobs:
run: npm ci

- name: "⬆️ Publish on NPM"
# we use publish library because flowr itself is a runnable, and we do not want to publish something with extra
# we use publish-library because flowr itself is a runnable, and we do not want to publish something with extra
# dist paths etc.
# besides, we make dead-sure we have a clean directory to work on!
run: |
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,13 @@
"lint": "npm run license-compat -- --summary && npx eslint --version && npx eslint src/ test/",
"license-compat": "license-checker --onlyAllow 'MIT;MIT OR X11;GPLv2;LGPL;GNUGPL;ISC;Apache-2.0;FreeBSD;BSD-2-Clause;clearbsd;ModifiedBSD;BSD-3-Clause;Python-2.0;Unlicense;WTFPL;BlueOak-1.0.0;CC-BY-4.0;CC-BY-3.0;CC0-1.0;0BSD'",
"doc": "typedoc",
"test": "vitest --config test/vitest.config.mts",
"test": "vitest --exclude \"test/system-tests/**\" --config test/vitest.config.mts",
"test:system": "vitest --dir test/system-tests --config test/system-tests/vitest.config.mts",
"test:coverage": "npm run test -- --coverage",
"performance-test": "func() { cd test/performance/ && bash run-all-suites.sh $1 $2 $3; cd ../../; }; func",
"test-full": "npm run test:coverage -- --no-watch -- --make-summary --test-installation",
"detect-circular-deps": "npx madge --extensions ts,tsx --circular src/",
"checkup": "npm run flowr -- --execute \":version\" && npm run lint && npm run test-full -- --allowOnly=false && docker build -t test-flowr -f scripts/Dockerfile . && npm run doc && npm-run-all wiki:*"
"checkup": "npm run flowr -- --execute \":version\" && npm run lint && npm run test-full -- --allowOnly=false && npm run test:system -- --no-watch && docker build -t test-flowr -f scripts/Dockerfile . && npm run doc && npm-run-all wiki:*"
},
"keywords": [
"static code analysis",
Expand Down
13 changes: 7 additions & 6 deletions test/functionality/r-bridge/normalize-ast-fold.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@ import type { RExpressionList } from '../../../src/r-bridge/lang-4.x/ast/model/n

describe('normalize-visitor', withShell(shell => {
let normalized: NormalizedAst | undefined;
let mathAst: NormalizedAst | undefined;
beforeAll(async() => {
normalized = await retrieveNormalizedAst(shell, 'x <- 42\ny <- "hello world"\nprint("foo")');
mathAst = await retrieveNormalizedAst(shell, '1 + 3 * 2');

});
test('find the number', () => {
let marker = false;
Expand Down Expand Up @@ -42,7 +45,7 @@ describe('normalize-visitor', withShell(shell => {
const result = astFold.fold(normalized?.ast);
expect(result).toBe(2);
});
test('do basic math (monoid)', async() => {
test('do basic math (monoid)', () => {
class MyMathFold<Info> extends DefaultNormalizedAstFold<number, Info> {
constructor() {
super(0);
Expand All @@ -67,11 +70,10 @@ describe('normalize-visitor', withShell(shell => {
}
}
const astFold = new MyMathFold();
const math = await retrieveNormalizedAst(shell, '1 + 3 * 2');
const result = astFold.fold(math?.ast);
const result = astFold.fold(mathAst?.ast);
expect(result).toBe(7);
});
test('fold should stop if overwritten and no continue', async() => {
test('fold should stop if overwritten and no continue', () => {
let foundNumber = false;
class MyMathFold<Info> extends DefaultNormalizedAstFold<void, Info> {
override foldRNumber(_node: RNumber<Info>) {
Expand All @@ -83,8 +85,7 @@ describe('normalize-visitor', withShell(shell => {
}
}
const astFold = new MyMathFold();
const math = await retrieveNormalizedAst(shell, '1 + 3 * 2');
astFold.fold(math?.ast);
astFold.fold(mathAst?.ast);
expect(foundNumber).toBe(false);
});
}));
16 changes: 16 additions & 0 deletions test/system-tests/commands.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { assert, describe, test } from 'vitest';
import { run } from './utility/utility';


describe('commands', () => {
test('flowr as server', async() => {
const expected = 'Server listening on port';
const output = await run('npm run flowr -- --server', expected);
assert.include(output, expected);
});

test('slicer', async() => {
const output = await run('npm run slicer -- -c "3@a" -r "a <- 3\\nb <- 4\\nprint(a)"');
assert.include(output, 'a <- 3\na');
});
});
24 changes: 24 additions & 0 deletions test/system-tests/repl.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { assert, describe, test } from 'vitest';
import { flowrRepl } from './utility/utility';

describe('repl', () => {
test(':df', async() => {
const output = await flowrRepl([':df test', ':quit']);
assert.include(output, 'flowchart');
});

test(':df x <- 3', async() => {
const output = await flowrRepl([':df x <- 3 ', ':quit']);
assert.include(output, 'flowchart');
});

test(':df "x <- 3\nprint(x)"', async() => {
const output = await flowrRepl([':df "x <- 3\\nprint(x)"', ':quit']);
assert.include(output, 'flowchart');
});

test(':slicer', async() => {
const output = await flowrRepl([':slicer -c "3@a" -r "a <- 3\\nb <- 4\\nprint(a)"', ':quit']);
assert.include(output, 'a <- 3\na');
});
});
70 changes: 70 additions & 0 deletions test/system-tests/utility/utility.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { exec } from 'child_process';

/**
* Runs the flowr repl and feeds input to the repl
* @param input - input to feed
* @returns Repl Output
*/

export async function flowrRepl(input: string[]): Promise<string> {
const process = new Promise<string>((resolve, reject) => {
const child = exec('npm run flowr', { timeout: 60 * 1000 }, (error, stdout, _) => {
if(error) {
reject(new Error(`${error.name}: ${error.message}\n${stdout}`));
}

resolve(stdout);
});

// Send new data when flowr sends us the 'R>' prompt to avoid
// sending data too fast
let i = 0;
child.stdout?.on('data', (d) => {
const data = d as Buffer;

if(data.toString().includes('R>')) {
if(i < input.length) {
child.stdin?.write(`${input[i++]}\n`);
}
}
});
});

return await process;
}

/**
* Runs a command and terminates it automatically if it outputs a certain string
* This is useful, so we don't have to set timeouts and hope the output will be produced in time.
*
* @param command - Command to run
* @param terminateOn - (optional) string to kill the process on
* @param timeout - (optional) timeout in milliseconds
* @returns output of command
*/
export async function run(command: string, terminateOn?: string, timeout = 60 * 1000): Promise<string> {
const process = new Promise<string>((resolve, reject) => {
const child = exec(command, { timeout }, (error, stdout, _) => {
if(error) {
reject(new Error(`${error.name}: ${error.message}\n${stdout}`));
}

resolve(stdout);
});

if(terminateOn) {
let buffer = '';
child.stdout?.on('data', (d: Buffer) => {
buffer += d.toString();

if(buffer.includes(terminateOn)) {
child.kill('SIGKILL');
resolve(buffer);
}
});
}
});

return await process;
}

22 changes: 22 additions & 0 deletions test/system-tests/vitest.config.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { defineConfig } from 'vitest/config';

export default defineConfig({
test: {
testTimeout: 60 * 2000,
sequence: {
/* each test file that does not support parallel execution will be executed in sequence by stating this explicitly */
concurrent: true,
setupFiles: 'parallel'
},
reporters: process.env.GITHUB_ACTIONS ? ['basic', 'github-actions'] : ['dot'],
isolate: false,
pool: 'threads',
deps: {
optimizer: {
ssr: {
enabled: true
}
}
}
},
});

2 comments on commit 0a57e68

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"artificial" Benchmark Suite

Benchmark suite Current: 0a57e68 Previous: db7ac2e Ratio
Retrieve AST from R code 237.68530227272728 ms (98.66504059373398) 242.24415118181818 ms (104.26310147093089) 0.98
Normalize R AST 17.253183545454547 ms (30.54154579978084) 17.39715159090909 ms (31.09452227247912) 0.99
Produce dataflow information 63.3047335 ms (141.04477116891695) 60.33616290909091 ms (127.75990956872062) 1.05
Total per-file 828.229115 ms (1493.4796936583705) 833.5976102727273 ms (1502.9439844838432) 0.99
Static slicing 2.0327492495862898 ms (1.1986348658053698) 2.029558401836784 ms (1.2081663161038019) 1.00
Reconstruct code 0.23152153062389233 ms (0.1733261895732629) 0.23538053186242114 ms (0.17968366955783688) 0.98
Total per-slice 2.277994332634675 ms (1.2620198633086381) 2.278853130617715 ms (1.2795408953549086) 1.00
failed to reconstruct/re-parse 0 # 0 # 1
times hit threshold 0 # 0 # 1
reduction (characters) 0.7891949660994808 # 0.7891949660994808 # 1
reduction (normalized tokens) 0.7665650684287274 # 0.7665650684287274 # 1
memory (df-graph) 95.46617542613636 KiB (244.77619956879823) 95.46617542613636 KiB (244.77619956879823) 1

This comment was automatically generated by workflow using github-action-benchmark.

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"social-science" Benchmark Suite

Benchmark suite Current: 0a57e68 Previous: db7ac2e Ratio
Retrieve AST from R code 239.30871009999998 ms (43.27095668706109) 237.81388206 ms (43.26435892465071) 1.01
Normalize R AST 18.830496 ms (14.414331605000688) 18.584648960000003 ms (13.710753719051512) 1.01
Produce dataflow information 72.23594578 ms (66.90833939413633) 71.41246154000001 ms (65.6106108648961) 1.01
Total per-file 7634.2038686000005 ms (29047.300746916673) 7625.11364774 ms (29095.600821943466) 1.00
Static slicing 15.761166074529504 ms (44.4601733125231) 15.746698561898643 ms (44.50249372495068) 1.00
Reconstruct code 0.2493108138800615 ms (0.14770836830816605) 0.24344144772080362 ms (0.13994668646932817) 1.02
Total per-slice 16.018184013370995 ms (44.49503588444086) 15.997988500166153 ms (44.53599655284285) 1.00
failed to reconstruct/re-parse 0 # 0 # 1
times hit threshold 0 # 0 # 1
reduction (characters) 0.8762109251198998 # 0.8762109251198998 # 1
reduction (normalized tokens) 0.819994064355517 # 0.819994064355517 # 1
memory (df-graph) 99.526015625 KiB (113.60201607005874) 99.526015625 KiB (113.60201607005874) 1

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.