diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 09c1edb..aff8049 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,11 +2,80 @@ name: Tests on: [ push ] jobs: + build-wabt: + name: Build WAT tools + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Get WABT commit ID + working-directory: tests/artifacts/wabt + run: echo "WABT_VERSION=$(git rev-parse HEAD)" >> $GITHUB_ENV + + - name: Cache wabt + uses: actions/cache@v4 + id: cache-wabt + with: + key: ${{ runner.os }}-wabt-${{ env.WABT_VERSION }} + path: tests/artifacts/wabt + restore-keys: | + ${{ runner.os }}-wabt- + + - name: Build WABT # Build latest version + if: steps.cache-wabt.outputs.cache-hit == false + working-directory: tests/artifacts/wabt + run: | + mkdir build; cd build + cmake .. + cmake --build . + + - name: Upload built tools + uses: actions/upload-artifact@v4 + with: + name: wabt-build-${{ github.run_id }} + path: tests/artifacts/wabt/build + + build-wdcli: + name: Build WARDuino CLI + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Get WDCLI commit ID + working-directory: tests/artifacts/warduino + run: echo "WDCLI_VERSION=$(git rev-parse HEAD)" >> $GITHUB_ENV + + - name: Cache WARDuino CLI + uses: actions/cache@v4 + id: cache-warduino + with: + key: ${{ runner.os }}-warduino-${{ env.WDCLI_VERSION }} + path: tests/artifacts/warduino + restore-keys: | + ${{ runner.os }}-warduino- + + - name: Build WARDuino CLI # Build latest version + if: steps.cache-warduino.outputs.cache-hit == false + working-directory: tests/artifacts/warduino + run: | + mkdir build; cd build + cmake .. -D BUILD_EMULATOR=ON + cmake --build . + + - name: Upload built tools + uses: actions/upload-artifact@v4 + with: + name: warduino-build-${{ github.run_id }} + path: tests/artifacts/warduino/build + unit: name: Unit tests runs-on: ubuntu-latest - strategy: - fail-fast: false + needs: build-wabt if: github.event.pull_request.draft == false steps: - uses: actions/checkout@v4 @@ -16,14 +85,26 @@ jobs: - name: Prebuild files run: npm run test:prebuild + - name: Download WAT tools + uses: actions/download-artifact@v4 + with: + name: wabt-build-${{ github.run_id }} + path: .tools + + - name: Verify tools + run: | + chmod u+x $GITHUB_WORKSPACE/.tools/* + $GITHUB_WORKSPACE/.tools/wasm-objdump --version + - name: Run ava unit tests run: npm run test:ava + env: + WABT: ${GITHUB_WORKSPACE}/.tools coverage: name: Code coverage runs-on: ubuntu-latest - strategy: - fail-fast: true + needs: build-wabt if: github.event.pull_request.draft == false steps: - uses: actions/checkout@v4 @@ -33,9 +114,26 @@ jobs: - name: Prebuild files run: npm run test:prebuild + - name: Download WAT tools + uses: actions/download-artifact@v4 + with: + name: wabt-build-${{ github.run_id }} + path: .tools + + - name: Verify tools + run: | + chmod u+x $GITHUB_WORKSPACE/.tools/* + $GITHUB_WORKSPACE/.tools/wasm-objdump --version + + - name: Add .tools to PATH + run: echo "$GITHUB_WORKSPACE/.tools" >> $GITHUB_PATH + - name: Run c8 coverage run: | - echo "LINE_COVERAGE=$(npm run coverage:test:ava | grep -E '^\s*Lines\s*:\s*[0-9]+(\.[0-9]+)?%' | awk '{print $3}' | tr -d '%')" >> $GITHUB_ENV + echo "LINE_COVERAGE=$(npm run coverage:test:ava | grep -E 'Lines\s*:\s*[0-9]+(\.[0-9]+)?%' | awk '{print $3}' | tr -d '%')" >> $GITHUB_ENV + + - name: Report coverage + run: npx c8 report --all - name: Update coverage badge uses: schneegans/dynamic-badges-action@v1.7.0 diff --git a/.gitmodules b/.gitmodules index 70c311a..463c443 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ -[submodule "WABT"] - path = WABT - url = git@github.com:TOPLLab/wabt.git \ No newline at end of file +[submodule "tests/unit/artifacts/wabt"] + path = tests/artifacts/wabt + url = git@github.com:TOPLLab/wabt.git +[submodule "tests/unit/artifacts/warduino"] + path = tests/artifacts/warduino + url = git@github.com:TOPLLab/WARDuino.git diff --git a/package.json b/package.json index 4a9cdeb..6dfb99a 100644 --- a/package.json +++ b/package.json @@ -1,68 +1,71 @@ { - "name": "latch", - "description": "A testing language for constraint environments", - "version": "0.3.1", - "type": "commonjs", - "exports": { - "types": "./dist/types/index.d.ts", - "require": "./dist/cjs/index.cjs" - }, - "main": "dist/cjs/index.cjs", - "types": "dist/types/index.d.ts", + "name": "latch", + "description": "A testing language for constraint environments", + "version": "0.3.1", + "type": "commonjs", + "exports": { + "types": "./dist/types/index.d.ts", + "require": "./dist/cjs/index.cjs" + }, + "main": "dist/cjs/index.cjs", + "types": "dist/types/index.d.ts", + "files": [ + "dist", + "bin" + ], + "scripts": { + "clean": "rm -rf dist", + "build": "npm run build:cjs", + "build:cjs": "tsc --project tsconfig.build.json", + "build:tests": "tsc --outDir out --project tsconfig.tests.json", + "watch": "tsc -watch -p ./", + "lint": "eslint src/**/* --config .eslint.config.mjs", + "test:prebuild": "npm run build && npm run build:tests", + "test:all": "npm run test:prebuild && npm run test:ava", + "test:ava": "ava", + "test:example": "npx ts-node ./tests/examples/example.ts", + "coverage:test:ava": "c8 --src src/ --all ava" + }, + "dependencies": { + "ansi-colors": "^4.1.3", + "ieee754": "^1.2.1", + "ora": "^8.0.1", + "source-map": "^0.7.4", + "ts-node": "^10.5.0", + "tslib": "^2.8.1" + }, + "devDependencies": { + "@ava/typescript": "^5.0.0", + "@eslint/js": "^9.16.0", + "@stylistic/eslint-plugin-js": "^2.11.0", + "@types/chai": "^4.3.0", + "@types/mocha": "^9.1.0", + "@types/node": "^20.10.4", + "@types/uuid": "^9.0.0", + "@types/ws": "^8.5.4", + "@typescript-eslint/eslint-plugin": "^8.17.0", + "ava": "^6.2.0", + "c8": "^10.1.2", + "convert-extension": "^0.3.0", + "eslint": "^9.16.0", + "globals": "^15.13.0", + "mqtt": "^4.3.7", + "serialport": "^10.4.0", + "typescript": "^5.2.0", + "typescript-eslint": "^8.17.0" + }, + "ava": { "files": [ - "dist", - "bin" + "out/tests/unit/describers.test.js", + "out/tests/unit/messaging.test.js", + "out/tests/unit/sourcemap.test.js", + "out/tests/unit/util.test.js" ], - "scripts": { - "clean": "rm -rf dist", - "build": "npm run build:cjs", - "build:cjs": "tsc --project tsconfig.build.json", - "build:tests": "tsc --outDir out --project tsconfig.tests.json", - "watch": "tsc -watch -p ./", - "lint": "eslint src/**/* --config .eslint.config.mjs", - "test:prebuild": "npm run build && npm run build:tests", - "test:all": "npm run test:prebuild && npm run test:ava", - "test:ava": "ava", - "test:example": "npx ts-node ./tests/examples/example.ts", - "coverage:test:ava": "c8 --src src/ --all ava" - }, - "dependencies": { - "ansi-colors": "^4.1.3", - "ieee754": "^1.2.1", - "ora": "^8.0.1", - "source-map": "^0.7.4", - "ts-node": "^10.5.0", - "tslib": "^2.8.1" - }, - "devDependencies": { - "@ava/typescript": "^5.0.0", - "@eslint/js": "^9.16.0", - "@stylistic/eslint-plugin-js": "^2.11.0", - "@types/chai": "^4.3.0", - "@types/mocha": "^9.1.0", - "@types/node": "^20.10.4", - "@types/uuid": "^9.0.0", - "@types/ws": "^8.5.4", - "@typescript-eslint/eslint-plugin": "^8.17.0", - "ava": "^6.2.0", - "c8": "^10.1.2", - "convert-extension": "^0.3.0", - "eslint": "^9.16.0", - "globals": "^15.13.0", - "mqtt": "^4.3.7", - "serialport": "^10.4.0", - "typescript": "^5.2.0", - "typescript-eslint": "^8.17.0" - }, - "ava": { - "files": [ - "out/tests/unit/util.test.js" - ], - "typescript": { - "compile": false, - "rewritePaths": { - "src/": "dist/" - } - } + "typescript": { + "compile": false, + "rewritePaths": { + "src/": "dist/" + } } + } } diff --git a/src/messaging/MessageQueue.ts b/src/messaging/MessageQueue.ts index ecb37b8..1da95fb 100644 --- a/src/messaging/MessageQueue.ts +++ b/src/messaging/MessageQueue.ts @@ -2,6 +2,9 @@ export class MessageQueue implements Iterable { private readonly delimiter: string; private queue: string[]; + /** + * @param delimiter the EOM (end-of-message) token + */ constructor(delimiter: string) { this.delimiter = delimiter; this.queue = []; diff --git a/src/reporter/Reporter.ts b/src/reporter/Reporter.ts index f1cdbd7..c88c643 100644 --- a/src/reporter/Reporter.ts +++ b/src/reporter/Reporter.ts @@ -119,19 +119,7 @@ export class Reporter { this.archiver.write(); } - info(text: string) { - this.output += `info: ${text}\n`; - } - error(text: string) { this.output += `error: ${text}\n`; } - - suite(title: string) { - this.output += `suite: ${title}\n`; - } - - test(title: string) { - this.output += ` test: ${title}\n`; - } } \ No newline at end of file diff --git a/src/reporter/describers/SuiteDescribers.ts b/src/reporter/describers/SuiteDescribers.ts index 73c7e4e..3d52609 100644 --- a/src/reporter/describers/SuiteDescribers.ts +++ b/src/reporter/describers/SuiteDescribers.ts @@ -62,10 +62,9 @@ export class NormalSuiteDescriber extends ShortSuiteDescriber { }); if (this.item.outcome === Outcome.error) { - report.push(' '.repeat(2) + red(this.item.clarification.toString())); + report.push(' '.repeat(2) + red(this.item.clarification)); } return report; } - } diff --git a/src/util/retry.ts b/src/util/retry.ts index 4670bc3..0ccd533 100644 --- a/src/util/retry.ts +++ b/src/util/retry.ts @@ -11,6 +11,6 @@ export function retry(promise: () => Promise, retries: number): Promise trying = ++attempt < retries; } } - reject(`exhausted number of retries (${retries})`); + reject(new Error(`exhausted number of retries (${retries})`)); }); } \ No newline at end of file diff --git a/src/util/util.ts b/src/util/util.ts index e55dd23..066c94f 100644 --- a/src/util/util.ts +++ b/src/util/util.ts @@ -1,7 +1,7 @@ export function getFileExtension(file: string): string { const result = /(?:\.([^.]+))?$/.exec(file) - if (result === null || result.length < 1) { - throw Error('Could not determine file type'); + if (result === null || result.length < 1 || result[1] === undefined) { + throw new Error('Could not determine file type'); } return result[1]; } diff --git a/tests/artifacts/compile.output b/tests/artifacts/compile.output new file mode 100644 index 0000000..e98befe --- /dev/null +++ b/tests/artifacts/compile.output @@ -0,0 +1,233 @@ +;; SOURCE MAP INFO +0000000: 0061 736d ; WASM_BINARY_MAGIC +0000004: 0100 0000 ; WASM_BINARY_VERSION +; section "Type" (1) +0000008: 01 ; section code +0000009: 0000 0000 00 ; section size (guess) +000000e: 03 ; num types +; func type 0 +000000f: 60 ; func +0000010: 02 ; num params +0000011: 7f ; i32 +0000012: 7f ; i32 +0000013: 00 ; num results +; func type 1 +0000014: 60 ; func +0000015: 01 ; num params +0000016: 7f ; i32 +0000017: 00 ; num results +; func type 2 +0000018: 60 ; func +0000019: 00 ; num params +000001a: 00 ; num results +0000009: 8d80 8080 00 ; FIXUP section size +; section "Import" (2) +000001b: 02 ; section code +000001c: 0000 0000 00 ; section size (guess) +0000021: 03 ; num imports +; import header 0 +0000022: 03 ; string length +0000023: 656e 76 env ; import module name +0000026: 0a ; string length +0000027: 6368 6970 5f64 656c 6179 chip_delay ; import field name +0000031: 00 ; import kind +0000032: 01 ; import signature index +; import header 1 +0000033: 03 ; string length +0000034: 656e 76 env ; import module name +0000037: 0d ; string length +0000038: 6368 6970 5f70 696e 5f6d 6f64 65 chip_pin_mode ; import field name +0000045: 00 ; import kind +0000046: 00 ; import signature index +; import header 2 +0000047: 03 ; string length +0000048: 656e 76 env ; import module name +000004b: 12 ; string length +000004c: 6368 6970 5f64 6967 6974 616c 5f77 7269 chip_digital_wri +000005c: 7465 te ; import field name +000005e: 00 ; import kind +000005f: 00 ; import signature index +000001c: bf80 8080 00 ; FIXUP section size +; section "Function" (3) +0000060: 03 ; section code +0000061: 0000 0000 00 ; section size (guess) +0000066: 02 ; num functions +0000067: 02 ; function 0 signature index +0000068: 02 ; function 1 signature index +0000061: 8380 8080 00 ; FIXUP section size +; section "Global" (6) +0000069: 06 ; section code +000006a: 0000 0000 00 ; section size (guess) +000006f: 03 ; num globals +0000070: 7f ; i32 +0000071: 00 ; global mutability +@ { "line": 13, "col_start": 21, "col_end": 30 } +0000072: 41 ; i32.const +0000073: 17 ; i32 literal +0000074: 0b ; end +0000075: 7f ; i32 +0000076: 00 ; global mutability +@ { "line": 14, "col_start": 20, "col_end": 29 } +0000077: 41 ; i32.const +0000078: 01 ; i32 literal +0000079: 0b ; end +000007a: 7f ; i32 +000007b: 00 ; global mutability +@ { "line": 15, "col_start": 21, "col_end": 30 } +000007c: 41 ; i32.const +000007d: 00 ; i32 literal +000007e: 0b ; end +000006a: 9080 8080 00 ; FIXUP section size +; section "Export" (7) +000007f: 07 ; section code +0000080: 0000 0000 00 ; section size (guess) +0000085: 01 ; num exports +0000086: 04 ; string length +0000087: 6d61 696e main ; export name +000008b: 00 ; export kind +000008c: 04 ; export func index +0000080: 8880 8080 00 ; FIXUP section size +; section "Code" (10) +000008d: 0a ; section code +000008e: 0000 0000 00 ; section size (guess) +0000093: 02 ; num functions +; function body 0 +0000094: 0000 0000 00 ; func body size (guess) +0000099: 00 ; local decl count +@ { "line": 20, "col_start": 5, "col_end": 15 } +000009a: 23 ; global.get +000009b: 00 ; global index +@ { "line": 21, "col_start": 5, "col_end": 14 } +000009c: 41 ; i32.const +000009d: 02 ; i32 literal +@ { "line": 22, "col_start": 5, "col_end": 9 } +000009e: 10 ; call +000009f: 01 ; function index +00000a0: 0b ; end +0000094: 8880 8080 00 ; FIXUP func body size +; function body 1 +00000a1: 0000 0000 00 ; func body size (guess) +00000a6: 01 ; local decl count +00000a7: 01 ; local type count +00000a8: 7f ; i32 +@ { "line": 28, "col_start": 5, "col_end": 14 } +00000a9: 41 ; i32.const +00000aa: e807 ; i32 literal +@ { "line": 29, "col_start": 5, "col_end": 14 } +00000ac: 21 ; local.set +00000ad: 00 ; local index +@ { "line": 32, "col_start": 5, "col_end": 9 } +00000ae: 10 ; call +00000af: 03 ; function index +@ { "line": 35, "col_start": 5, "col_end": 9 } +00000b0: 03 ; loop +00000b1: 40 ; void +@ { "line": 36, "col_start": 7, "col_end": 17 } +00000b2: 23 ; global.get +00000b3: 00 ; global index +@ { "line": 37, "col_start": 7, "col_end": 17 } +00000b4: 23 ; global.get +00000b5: 01 ; global index +@ { "line": 38, "col_start": 7, "col_end": 11 } +00000b6: 10 ; call +00000b7: 02 ; function index +@ { "line": 39, "col_start": 7, "col_end": 16 } +00000b8: 20 ; local.get +00000b9: 00 ; local index +@ { "line": 40, "col_start": 7, "col_end": 11 } +00000ba: 10 ; call +00000bb: 00 ; function index +@ { "line": 41, "col_start": 7, "col_end": 17 } +00000bc: 23 ; global.get +00000bd: 00 ; global index +@ { "line": 42, "col_start": 7, "col_end": 17 } +00000be: 23 ; global.get +00000bf: 02 ; global index +@ { "line": 43, "col_start": 7, "col_end": 11 } +00000c0: 10 ; call +00000c1: 02 ; function index +@ { "line": 44, "col_start": 7, "col_end": 16 } +00000c2: 20 ; local.get +00000c3: 00 ; local index +@ { "line": 45, "col_start": 7, "col_end": 11 } +00000c4: 10 ; call +00000c5: 00 ; function index +@ { "line": 46, "col_start": 7, "col_end": 9 } +00000c6: 0c ; br +00000c7: 00 ; break depth +00000c8: 0b ; end +00000c9: 0b ; end +00000a1: a480 8080 00 ; FIXUP func body size +000008e: b780 8080 00 ; FIXUP section size +; section "name" +00000ca: 00 ; section code +00000cb: 0000 0000 00 ; section size (guess) +00000d0: 04 ; string length +00000d1: 6e61 6d65 name ; custom section name +00000d5: 01 ; name subsection type +00000d6: 0000 0000 00 ; subsection size (guess) +00000db: 05 ; num names +00000dc: 00 ; elem index +00000dd: 0e ; string length +00000de: 656e 762e 6368 6970 5f64 656c 6179 env.chip_delay ; elem name 0 +00000ec: 01 ; elem index +00000ed: 11 ; string length +00000ee: 656e 762e 6368 6970 5f70 696e 5f6d 6f64 env.chip_pin_mod +00000fe: 65 e ; elem name 1 +00000ff: 02 ; elem index +0000100: 16 ; string length +0000101: 656e 762e 6368 6970 5f64 6967 6974 616c env.chip_digital +0000111: 5f77 7269 7465 _write ; elem name 2 +0000117: 03 ; elem index +0000118: 04 ; string length +0000119: 696e 6974 init ; elem name 3 +000011d: 04 ; elem index +000011e: 05 ; string length +000011f: 626c 696e 6b blink ; elem name 4 +00000d6: c980 8080 00 ; FIXUP subsection size +0000124: 02 ; local name type +0000125: 0000 0000 00 ; subsection size (guess) +000012a: 05 ; num functions +000012b: 00 ; function index +000012c: 00 ; num locals +000012d: 01 ; function index +000012e: 00 ; num locals +000012f: 02 ; function index +0000130: 00 ; num locals +0000131: 03 ; function index +0000132: 00 ; num locals +0000133: 04 ; function index +0000134: 01 ; num locals +0000135: 00 ; local index +0000136: 05 ; string length +0000137: 6465 6c61 79 delay ; local name 0 +0000125: 9280 8080 00 ; FIXUP subsection size +000013c: 04 ; name subsection type +000013d: 0000 0000 00 ; subsection size (guess) +0000142: 03 ; num names +0000143: 00 ; elem index +0000144: 12 ; string length +0000145: 696e 7433 322d 3e69 6e74 3332 2d3e 766f int32->int32->vo +0000155: 6964 id ; elem name 0 +0000157: 01 ; elem index +0000158: 0b ; string length +0000159: 696e 7433 322d 3e76 6f69 64 int32->void ; elem name 1 +0000164: 02 ; elem index +0000165: 0a ; string length +0000166: 766f 6964 2d3e 766f 6964 void->void ; elem name 2 +000013d: ae80 8080 00 ; FIXUP subsection size +0000170: 07 ; name subsection type +0000171: 0000 0000 00 ; subsection size (guess) +0000176: 03 ; num names +0000177: 00 ; elem index +0000178: 03 ; string length +0000179: 6c65 64 led ; elem name 0 +000017c: 01 ; elem index +000017d: 02 ; string length +000017e: 6f6e on ; elem name 1 +0000180: 02 ; elem index +0000181: 03 ; string length +0000182: 6f66 66 off ; elem name 2 +0000171: 8f80 8080 00 ; FIXUP subsection size +00000cb: b581 8080 00 ; FIXUP section size + diff --git a/tests/artifacts/dump b/tests/artifacts/dump new file mode 100644 index 0000000..9baf762 --- /dev/null +++ b/tests/artifacts/dump @@ -0,0 +1,100 @@ + +blink.wasm: file format wasm 0x1 +, , , , , +Section Details: + +Type[4]: + - type[0] (i32, i32) -> nil + - type[1] (i32) -> i32 + - type[2] (i32) -> nil + - type[3] () -> nil +Import[4]: + - func[0] sig=2 <- env.chip_delay + - func[1] sig=0 <- env.chip_pin_mode + - func[2] sig=1 <- env.chip_digital_read + - func[3] sig=0 <- env.chip_digital_write +Function[2]: + - func[4] sig=3 + - func[5] sig=3 +Global[3]: + - global[0] i32 mutable=0 - init i32=23 + - global[1] i32 mutable=0 - init i32=1 + - global[2] i32 mutable=0 - init i32=0 +Export[1]: + - func[5] -> "main" +Code[2]: + - func[4] size=8 + - func[5] size=44 +Custom: + - name: "name" + - func[0] +, - func[1] +, - func[2] +, - func[3] +, - func[4] +, - func[5] + - func[5] local[0] + - type[0] int32->void> + - type[1] int32> + - type[2] void> + - type[3] void> + - global[0] + - global[1] + - global[2] + +Code Disassembly: + +0000b6 func[4] : + 0000b7: 23 00 | global.get 0 + 0000b9: 41 02 | i32.const 2 + 0000bb: 10 01 | call 1 + 0000bd: 0b | end +0000c3 func[5] : + 0000c4: 01 7f | local[0] type=i32 + 0000c6: 41 e8 07 | i32.const 1000 + 0000c9: 21 00 | local.set 0 + 0000cb: 10 04 | call 4 + 0000cd: 03 40 | loop + 0000cf: 23 00 | global.get 0 + 0000d1: 23 01 | global.get 1 + 0000d3: 10 03 | call 3 + 0000d5: 20 00 | local.get 0 + 0000d7: 10 00 | call 0 + 0000d9: 23 00 | global.get 0 + 0000db: 10 02 | call 2 + 0000dd: 23 00 | global.get 0 + 0000df: 23 02 | global.get 2 + 0000e1: 10 03 | call 3 + 0000e3: 20 00 | local.get 0 + 0000e5: 10 00 | call 0 + 0000e7: 23 00 | global.get 0 + 0000e9: 10 02 | call 2 + 0000eb: 0c 00 | br 0 + 0000ed: 0b | end + 0000ee: 0b | end +, , , , , +Sourcemap JSON: + +{ + "Functions": [ + {"name": "env.chip_delay", + "locals": [] + }, + {"name": "env.chip_pin_mode", + "locals": [] + }, + {"name": "env.chip_digital_read", + "locals": [] + }, + {"name": "env.chip_digital_write", + "locals": [] + }, + {"name": "init", + "locals": [] + }, + {"name": "blink", + "locals": [{"name": "delay","idx":0}] + } + ] +} + diff --git a/tests/artifacts/upload.wasm b/tests/artifacts/upload.wasm new file mode 100644 index 0000000..312589a Binary files /dev/null and b/tests/artifacts/upload.wasm differ diff --git a/tests/artifacts/wabt b/tests/artifacts/wabt new file mode 160000 index 0000000..09d542f --- /dev/null +++ b/tests/artifacts/wabt @@ -0,0 +1 @@ +Subproject commit 09d542f48e03a55b73be47b981edff0793b3a6f1 diff --git a/tests/artifacts/warduino b/tests/artifacts/warduino new file mode 160000 index 0000000..3e97e4f --- /dev/null +++ b/tests/artifacts/warduino @@ -0,0 +1 @@ +Subproject commit 3e97e4f43f12b01c65fd4ad2f9f418010852c486 diff --git a/tests/examples/example.ts b/tests/examples/example.ts index 71c690d..d2827d7 100644 --- a/tests/examples/example.ts +++ b/tests/examples/example.ts @@ -19,7 +19,7 @@ const framework = Framework.getImplementation(); const spec = framework.suite('Test Wasm spec'); // must be called first -spec.testee('emulator[:8500]', new EmulatorSpecification(8500)); +spec.testee('emulator[:8100]', new EmulatorSpecification(8100)); const steps: Step[] = []; @@ -40,9 +40,7 @@ spec.test({ }); const debug = framework.suite('Test Debugger interface'); -debug.testee('emulator[:8520]', new EmulatorSpecification(8520)); -debug.testee('emulator[:8522]', new EmulatorSpecification(8522)); -// debug.testee('esp wrover', new ArduinoSpecification('/dev/ttyUSB0', 'esp32:esp32:esp32wrover'), new HybridScheduler(), {connectionTimout: 0}); +debug.testee('emulator[:8150]', new EmulatorSpecification(8150)); debug.test({ title: 'Test STEP OVER', @@ -96,91 +94,4 @@ debug.test({ steps: [DUMP] }); -const primitives = framework.suite('Test primitives'); - -primitives.testee('debug[:8700]', new EmulatorSpecification(8700)); - -primitives.test({ - title: `Test store primitive`, - program: 'tests/examples/dummy.wast', - dependencies: [], - steps: [{ - title: 'CHECK: execution at start of main', - instruction: {kind: Kind.Request, value: dump}, - expected: [{'pc': {kind: 'primitive', value: 129} as Expected}] - }, - - new Invoker('load', [WASM.i32(32)], WASM.i32(0)), - - { - title: 'Send STEP command', - instruction: {kind: Kind.Request, value: step} - }, - - { - title: 'Send STEP command', - instruction: {kind: Kind.Request, value: step} - }, - - { - title: 'Send STEP command', - instruction: {kind: Kind.Request, value: step} - }, - - new Invoker('load', [WASM.i32(32)], WASM.i32(42)) - ] -}) - -const oop = framework.suite('Test Out-of-place primitives'); - -oop.testee('supervisor[:8100] - proxy[:8150]', new OutofPlaceSpecification(8100, 8150)); - -oop.test({ - title: `Test store primitive`, - program: 'tests/examples/dummy.wast', - dependencies: [], - steps: [ - { - title: '[supervisor] CHECK: execution at start of main', - instruction: {kind: Kind.Request, value: dump}, - expected: [{'pc': {kind: 'primitive', value: 129} as Expected}] - }, - - { - title: '[proxy] CHECK: execution at start of main', - instruction: {kind: Kind.Request, value: dump}, - expected: [{'pc': {kind: 'primitive', value: 129} as Expected}], - target: Target.proxy - }, - - new Invoker('load', [WASM.i32(32)], WASM.i32(0), Target.proxy), - - { - title: '[supervisor] Send STEP command', - instruction: {kind: Kind.Request, value: step} - }, - - { - title: '[supervisor] Send STEP command', - instruction: {kind: Kind.Request, value: step} - }, - - { - title: '[supervisor] Send STEP command', - instruction: {kind: Kind.Request, value: step} - }, - - { - title: '[supervisor] CHECK: execution took three steps', - instruction: {kind: Kind.Request, value: dump}, - expected: [{'pc': {kind: 'primitive', value: 136} as Expected}] - }, - - new Invoker('load', [WASM.i32(32)], WASM.i32(42), Target.proxy), - - new Invoker('load', [WASM.i32(32)], WASM.i32(42), Target.supervisor) - ] -}); - - -framework.run([spec, debug, primitives, oop]).then(() => process.exit(0)); +framework.run([spec, debug]).then(() => process.exit(0)); diff --git a/tests/unit/describers.test.ts b/tests/unit/describers.test.ts new file mode 100644 index 0000000..6b0d94b --- /dev/null +++ b/tests/unit/describers.test.ts @@ -0,0 +1,46 @@ +import test from 'ava'; +import { + MinimalScenarioDescriber, + NormalScenarioDescriber, + ShortScenarioDescriber +} from '../../src/reporter/describers/ScenarioDescribers'; +import {ScenarioResult, StepOutcome, SuiteResult} from '../../src/reporter/Results'; +import {Kind, Message, Step} from '../../src'; +import {Outcome} from '../../src/reporter/describers/Describer'; +import {Plain} from '../../src/reporter/Style'; + +const steps: Step[] = [ + { + title: 'Send DUMP command', + instruction: {kind: Kind.Request, value: Message.dump}, + expected: [{'pc': {kind: 'comparison', value: (_: Object, value: number) => value > 0}}] + }, { + title: 'Send STEP OVER command', + instruction: {kind: Kind.Request, value: Message.stepOver} + } +]; +const dummy = new ScenarioResult({ + title: 'Scenario title', program: 'artifacts/blink.wat', steps: steps +}); + +test('[MinimalScenarioDescriber] : test printing', t => { + const describer = new MinimalScenarioDescriber(dummy); + dummy.aggregate([new StepOutcome(steps[0]).update(Outcome.succeeded), new StepOutcome(steps[1]).update(Outcome.succeeded)]); + t.is(describer.describe(new Plain()).join('\n'), '\x1B[1m\x1B[34mscenario.\x1B[39m\x1B[22m \x1B[1mScenario title\x1B[22m \n'); +}); + +test('[ShortScenarioDescriber] : test printing', t => { + const describer = new ShortScenarioDescriber(dummy); + const output = describer.describe(new Plain()).join('\n'); + t.is(output, '\x1B[1m\x1B[34mscenario.\x1B[39m\x1B[22m \x1B[1mScenario title\x1B[22m \n'); + t.false(['Send DUMP command', 'Send STEP OVER command'].some((element) => output.includes(element))); +}); + +test('[NormalScenarioDescriber] : test printing', t => { + let output = new NormalScenarioDescriber(dummy).describe(new Plain()).join('\n'); + t.true(['PASS', 'Send DUMP command', 'Send STEP OVER command'].every((element) => output.includes(element))); + t.false(['FAIL'].some((element) => output.includes(element))); + dummy.aggregate([new StepOutcome(steps[0]).update(Outcome.failed), new StepOutcome(steps[1]).update(Outcome.succeeded)]); + output = new NormalScenarioDescriber(dummy).describe(new Plain()).join('\n'); + t.true(['FAIL', 'Send DUMP command', 'PASS', 'Send STEP OVER command'].every((element) => output.includes(element))); +}); diff --git a/tests/unit/messaging.test.ts b/tests/unit/messaging.test.ts new file mode 100644 index 0000000..9f64070 --- /dev/null +++ b/tests/unit/messaging.test.ts @@ -0,0 +1,78 @@ +import test from 'ava'; +import {MessageQueue} from '../../src/messaging/MessageQueue'; + +const alphanumerical = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'.split(''); + +test('[MessageQueue] : test EOM detection', t => { + const fuzzer = fuzzy(alphanumerical); + const newline = new MessageQueue('\n'); + newline.push(fuzzer(9)); + t.false(newline.hasCompleteMessage()); + for (let i = 0; i < 5; i++) { + newline.push(fuzzer(20)); + } + t.false(newline.hasCompleteMessage()); + newline.push('\n'); + t.true(newline.hasCompleteMessage()); + + const semicolon = new MessageQueue(';'); + t.false(semicolon.hasCompleteMessage()); + semicolon.push(fuzzer(38) + ';' + fuzzer(3)); + t.true(newline.hasCompleteMessage()); + + const longer = new MessageQueue(' | '); + t.false(longer.hasCompleteMessage()); + longer.push(fuzzer(38) + ' | ' + fuzzer(3)); + t.true(longer.hasCompleteMessage()); +}); + +test('[MessageQueue] : test message retrieval', t => { + const fuzzer = fuzzy(alphanumerical); + + const message = fuzzy(alphanumerical)(30) + ';' + + const queue = new MessageQueue(';'); + queue.push(message); + queue.push(fuzzy(alphanumerical.concat([';']))(1028)); + t.is(queue.pop(), message); +}); + +test('[MessageQueue] : test iterator', t => { + const fuzzer = fuzzy(alphanumerical); + const queue = new MessageQueue(';'); + queue.push(fuzzer(16) + ';' + fuzzer(16) + ';' + fuzzer(16) + ';' + fuzzer(16)); + + let count = 0; + for (const message of queue) { + count++; + t.true(message.includes(';')); + t.is(message.length, 17); + } + t.true(count === 3); +}); + +test('[MessageQueue] : fuzz iterator', t => { + const EOM = ';' + const fuzzer = fuzzy(alphanumerical.concat([EOM])); + + for (let i = 0; i < 4096; i++) { + const input = fuzzer(2048); + const count = (input.match(new RegExp(`${EOM}`, 'g')) || []).length; + + const queue = new MessageQueue(EOM); + queue.push(input); + t.is([...queue].length, count); + } +}); + + +const fuzzy = (characters: string[]): (n: number) => string => { + const options = characters.length; + return (length: number) => { + let result = ''; + for (let counter = 0; counter < length; counter++) { + result += characters[Math.floor(Math.random() * options)]; + } + return result; + }; +} \ No newline at end of file diff --git a/tests/unit/sourcemap.test.ts b/tests/unit/sourcemap.test.ts new file mode 100644 index 0000000..6e21d96 --- /dev/null +++ b/tests/unit/sourcemap.test.ts @@ -0,0 +1,84 @@ +import test from 'ava'; +import {WASM} from '../../src/sourcemap/Wasm'; +import {WatMapper} from '../../src/sourcemap/SourceMapper'; +import {SourceMap} from '../../src/sourcemap/SourceMap'; +import {WABT} from '../../src/util/env'; +import {copyFileSync, mkdtempSync, readFileSync, rmSync} from 'fs'; + +const artifacts = `${__dirname}/../../../tests/artifacts`; + +/** + * Check LEB 128 encoding + */ +test('[leb128] : test encoding', t => { + t.is(WASM.leb128(0), '00'); + t.is(WASM.leb128(1), '01'); + t.is(WASM.leb128(8), '08'); + t.is(WASM.leb128(32), '20'); + t.is(WASM.leb128(64), 'C000'); + t.is(WASM.leb128(128), '8001'); + t.is(WASM.leb128(1202), 'B209'); +}); + +test('[extractLineInfo] : test against artifacts (1)', async t => { + await check(`${artifacts}/compile.output`, (mapping: SourceMap.Mapping) => { + // check line information + t.true(mapping.lines.some((entry) => + entry.line === 13 && + // entry.columnStart === 21 && + entry.columnEnd === 30 && + entry.instructions.some((entry) => + entry.address === 114))); + + t.true(mapping.lines.some((entry) => + entry.line === 22 && + // entry.columnStart === 5 && + entry.columnEnd === 9 && + entry.instructions.some((entry) => + entry.address === 158))); + }); +}) +; + +test('[extractImportInfo] : test against artifacts (1)', async t => { + await check(`${artifacts}/compile.output`, (mapping: SourceMap.Mapping) => { + // check imports + t.true(mapping.imports.some((entry) => entry.name.includes('chip_delay') && entry.index === 0)); + t.true(mapping.imports.some((entry) => entry.name.includes('chip_pin_mod') && entry.index === 1)); + t.true(mapping.imports.some((entry) => entry.name.includes('chip_digital') && entry.index === 2)); + }); +}); + +test('[extractGlobalInfo] : test against artifacts (1)', async t => { + await check(`${artifacts}/compile.output`, (mapping: SourceMap.Mapping) => { + // check globals + t.true(mapping.globals.some((entry) => entry.name.includes('led') && entry.index === 0)); + t.true(mapping.globals.some((entry) => entry.name.includes('on') && entry.index === 1)); + t.true(mapping.globals.some((entry) => entry.name.includes('off') && entry.index === 2)); + }); +}); + +test('[getFunctionInfos] : test against artifacts (1)', async t => { + await check(`${artifacts}/compile.output`, (mapping: SourceMap.Mapping) => { + // check functions + t.true(mapping.functions.some((entry) => entry.name.includes('blink') && entry.index === 4)); + }); +}); + +// run a set of _checks_ against the source mapping for a given _filename_ +async function check(filename: string, checks: (mapping: SourceMap.Mapping) => void) { + const dummy = readFileSync(filename).toString(); + const path = initialize(); + const mapper: WatMapper = new WatMapper(dummy, path, WABT); + const mapping: SourceMap.Mapping = await mapper.mapping(); + + checks(mapping); + + rmSync(path, {recursive: true}); +} + +function initialize(): string { + const tmp: string = mkdtempSync('test'); + copyFileSync(`${artifacts}/upload.wasm`, `${tmp}/upload.wasm`); + return tmp; +} \ No newline at end of file diff --git a/tests/unit/util.test.ts b/tests/unit/util.test.ts index 9708fc4..a0bbbee 100644 --- a/tests/unit/util.test.ts +++ b/tests/unit/util.test.ts @@ -1,9 +1,55 @@ import test from 'ava'; -import {getFileExtension} from '../../src/util/util'; +import {find, getFileExtension} from '../../src/util/util'; +import {indent} from '../../src/util/printing'; +import {retry} from '../../src/util/retry'; -test('[internal] test util/getFileExtension', t => { +test('[getFileExtension] : test core functionality', t => { t.is(getFileExtension('test.wast'), 'wast'); t.is(getFileExtension('util.test.js'), 'js'); t.is(getFileExtension('float_misc.asserts.wast'), 'wast'); t.is(getFileExtension('simd_i16x8_extadd_pairwise_i8x16.wast'), 'wast'); }); + +test('[getFileExtension] : test error throwing', t => { + const error = t.throws(() => { + getFileExtension('impossible') + }, {instanceOf: Error}); + t.is(error.message, 'Could not determine file type'); +}); + +test('[find] : test core functionality', t => { + t.is(find(/(wast)/, 'test.wast'), 'wast'); + t.is(find(/(.wast)/, 'impossible'), ''); + t.is(find(/—(.*)—/, 'Jeeves—my man, you know—is really a most extraordinary chap.'), 'my man, you know'); + t.is(find(/^(?:[^,]*,){2}\s*([a-zA-Z]+)/, 'liberty, equality, fraternity'), 'fraternity'); + t.is(find(/^(?:[^,]*,){2}\s*([a-zA-Z]+)/, 'The unanimous Declaration of the thirteen united States of America, When in the Course of human events, it becomes necessary for one people to dissolve the political bands which have connected them with another, and to assume among the powers of the earth, the separate and equal station to which the Laws of Nature and of Nature\'s God entitle them, a decent respect to the opinions of mankind requires that they should declare the causes which impel them to the separation.'), 'it'); + t.is(find(/([a-zA-Z]{11,})/, 'The unanimous Declaration of the thirteen united States of America, When in the Course of human events, it becomes necessary for one people to dissolve the political bands which have connected them with another, and to assume among the powers of the earth, the separate and equal station to which the Laws of Nature and of Nature\'s God entitle them, a decent respect to the opinions of mankind requires that they should declare the causes which impel them to the separation.'), 'Declaration'); +}); + +test('[indent] : test printing', t => { + t.is(indent(4), ' '); + t.is(indent(4, 1), ' '); + t.is(indent(1, 4), ' '); +}); + +const attempt = (count: number, n: number) => retry(async () => { + return new Promise((resolve, reject) => { + count--; + if (count === 0) { + resolve(0); + } else { + reject() + } + }) +}, n); + +test('[retry] : test core functionality', async t => { + t.is(await attempt(2, 2), 0); + t.is(await attempt(2, 4), 0); + t.is(await attempt(8, 9), 0); +}); + +test('[retry] : test error throwing', async t => { + const error = await t.throwsAsync(attempt(2, 1)); + t.is(error.message, `exhausted number of retries (${1})`); +}); \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index e01c580..99b39c1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,9 +16,10 @@ "allowJs": true }, "exclude": [ - "WABT", + "wabt", "node_modules", "dist", - "tests/examples" + "tests/examples", + "warduino" ] }