diff --git a/leo/tests/mod.rs b/leo/tests/mod.rs index 8a39330221..b6fc61a953 100644 --- a/leo/tests/mod.rs +++ b/leo/tests/mod.rs @@ -20,7 +20,7 @@ use std::path::PathBuf; use crate::{ commands::{ package::{Login, Logout}, - Build, Command, Prove, Run, Setup, Test, Update, UpdateAutomatic, + Build, Command, Prove, Run, Setup, Test, }, context::{create_context, Context}, }; @@ -176,6 +176,8 @@ pub fn login_incorrect_credentials_or_token() -> Result<()> { #[cfg(not(target_os = "macos"))] #[test] pub fn leo_update_and_update_automatic() -> Result<()> { + use crate::commands::{Update, UpdateAutomatic}; + let update = Update { list: true, studio: true, diff --git a/wasm/Cargo.toml b/wasm/Cargo.toml index 2ae13de171..6838d0ecab 100644 --- a/wasm/Cargo.toml +++ b/wasm/Cargo.toml @@ -37,6 +37,10 @@ path = "../asg" version = "1.5.3" path = "../ast" +[dependencies.leo-compiler] +version = "1.5.3" +path = "../compiler" + [dependencies.leo-ast-passes] version = "1.5.3" path = "../ast-passes" diff --git a/wasm/tests/.gitignore b/wasm/tests/.gitignore new file mode 100644 index 0000000000..f06235c460 --- /dev/null +++ b/wasm/tests/.gitignore @@ -0,0 +1,2 @@ +node_modules +dist diff --git a/wasm/tests/package-lock.json b/wasm/tests/package-lock.json new file mode 100644 index 0000000000..065afe7326 --- /dev/null +++ b/wasm/tests/package-lock.json @@ -0,0 +1,87 @@ +{ + "name": "parser-wasm-tests", + "version": "0.1.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "parser-wasm-tests", + "version": "0.1.0", + "license": "GNU", + "dependencies": { + "@types/js-yaml": "^4.0.3", + "@types/node": "^16.9.4", + "js-yaml": "^4.1.0", + "typescript": "^3.9.10" + } + }, + "node_modules/@types/js-yaml": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.3.tgz", + "integrity": "sha512-5t9BhoORasuF5uCPr+d5/hdB++zRFUTMIZOzbNkr+jZh3yQht4HYbRDyj9fY8n2TZT30iW9huzav73x4NikqWg==" + }, + "node_modules/@types/node": { + "version": "16.9.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.4.tgz", + "integrity": "sha512-KDazLNYAGIuJugdbULwFZULF9qQ13yNWEBFnfVpqlpgAAo6H/qnM9RjBgh0A0kmHf3XxAKLdN5mTIng9iUvVLA==" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/typescript": { + "version": "3.9.10", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.10.tgz", + "integrity": "sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q==", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + } + }, + "dependencies": { + "@types/js-yaml": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.3.tgz", + "integrity": "sha512-5t9BhoORasuF5uCPr+d5/hdB++zRFUTMIZOzbNkr+jZh3yQht4HYbRDyj9fY8n2TZT30iW9huzav73x4NikqWg==" + }, + "@types/node": { + "version": "16.9.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.4.tgz", + "integrity": "sha512-KDazLNYAGIuJugdbULwFZULF9qQ13yNWEBFnfVpqlpgAAo6H/qnM9RjBgh0A0kmHf3XxAKLdN5mTIng9iUvVLA==" + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "requires": { + "argparse": "^2.0.1" + } + }, + "typescript": { + "version": "3.9.10", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.10.tgz", + "integrity": "sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q==" + } + } +} diff --git a/wasm/tests/package.json b/wasm/tests/package.json new file mode 100644 index 0000000000..5a0653bfcf --- /dev/null +++ b/wasm/tests/package.json @@ -0,0 +1,25 @@ +{ + "name": "parser-wasm-tests", + "version": "0.1.0", + "description": "Test Framework implementation for compiler tests in WASM", + "main": "index.js", + "scripts": { + "compile": "tsc -p ./" + }, + "repository": { + "type": "git", + "url": "git+ssh://git@github.com/aleohq/leo.git" + }, + "author": "The Aleo Team ", + "license": "GNU", + "bugs": { + "url": "https://github.com/aleohq/leo/issues" + }, + "homepage": "https://github.com/aleohq/leo#readme", + "dependencies": { + "@types/js-yaml": "^4.0.3", + "@types/node": "^16.9.4", + "js-yaml": "^4.1.0", + "typescript": "^3.9.10" + } +} diff --git a/wasm/tests/src/index.ts b/wasm/tests/src/index.ts new file mode 100644 index 0000000000..990cac2e40 --- /dev/null +++ b/wasm/tests/src/index.ts @@ -0,0 +1,180 @@ +// Copyright (C) 2019-2021 Aleo Systems Inc. +// This file is part of the Leo library. + +// The Leo library is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The Leo library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with the Leo library. If not, see . + +import * as fs from 'fs'; +import * as path from 'path'; +import * as parser from '../../pkg/leo_wasm'; +import * as yaml from 'js-yaml'; +import {JSON_SCHEMA, CORE_SCHEMA, DEFAULT_SCHEMA} from 'js-yaml'; + +// Path to the parser tests folder +const TESTS_PATH: string = path.join(__dirname, '../../../tests/parser'); + +// Path to the test expectations for parser tests +const EXPECTATIONS_PATH: string = path.join(__dirname, '../../../tests/expectations/parser/parser'); + +// List of folders containing parser tests +const TEST_FOLDERS: string[] = fs.readdirSync(TESTS_PATH); + +// Test expectations enum, maps to string values: Pass and Fail +enum Expectation { + Pass = "Pass", + Fail = "Fail" +} + +interface TestHeader { + expectation: Expectation, + namespace: string +} + +interface TestResult { + filePath: string, + expectation: Expectation, + received: Expectation, + error: string|null +} + +// Main function for the tests +(function runTests() { + let results: TestResult[] = []; + + for (let folder of TEST_FOLDERS) { + results.push(...walkDir(folder)); + } + + if (results.length !== 0) { + results.forEach((result) => { + console.log( + 'Test failed. Path: %s; Expected: %s; Received: %s', + result.filePath, + result.expectation, + result.received + ); + }); + process.exit(1); + } else { + console.log('All tests successfully passed.'); + process.exit(0); + } +})(); + + +/** + * Test specific file an compare its outputs to the test expectations. + * + * @param filePath Path to the tested file + * @param outFile Contents of the expectation file of there is one + * @returns {TestResult[]} Tests that failed execution, empty means success + */ +function test(filePath: string, outFile: string|null): TestResult[] { + const text = fs.readFileSync(filePath).toString(); + + // Process the test's contents by cutting off the header + const header = text.slice(text.indexOf('/*') + 2, text.indexOf('*/')); + const testBody = text.slice(text.indexOf('*/') + 2); + + const {expectation /* , namespace */} = readHeader(header); + + const mismatches: TestResult[] = []; + const outputs: string[] = []; + + // Get the tests by splitting by 2 newlines and sanitize each sample. + const samples = testBody + .split('\n\n') + .map((el) => el.trim()) + .filter((el) => el.length !== 0); + + // Go through each test and run WASM parse function. Check the results agains expectations + // and collect outputs to later compare to saved .out expectations. + for (const sample of samples) { + try { + outputs.push(JSON.parse(parser.parse(sample))); // Parser outputs JSON + if (expectation === Expectation.Fail) { // If expectation was Fail and it passed + mismatches.push({ + filePath, + expectation: Expectation.Fail, + received: Expectation.Pass, + error: null + }); + } + } catch (error) { + outputs.push(error.toString()); + if (expectation === Expectation.Pass) { // If expectation was Pass and it failed + mismatches.push({ + error, + filePath, + expectation: Expectation.Pass, + received: Expectation.Fail, + }); + } + } + } + + // Todo: After AST spans are removed, figure out a way to canonicalize strings + // and get them to the same format as serde's. For now comparing the outputs as + // strings is impossible. + // + // const formedOut = yaml.dump({ + // expectation: expectation, + // namespace: namespace, + // outputs: outputs + // }, {schema: DEFAULT_SCHEMA}); + + return mismatches; +} + +/** + * Recursively go through each directory. If a file is met, + * then run test function for this file. + * + * @param fileOrDir + */ +function walkDir(fileOrDir: string): TestResult[] { + const currTarget = path.join(TESTS_PATH, fileOrDir); + let results: TestResult[] = []; // collect test results from sub calls + + if (fs.lstatSync(currTarget).isDirectory()) { + for (const entry of fs.readdirSync(currTarget)) { + results.push(...walkDir(path.join(fileOrDir, entry))); + } + } else { + const outFilePath = path.join(EXPECTATIONS_PATH, fileOrDir + '.out'); + const outFile = fs.existsSync(outFilePath) ? fs.readFileSync(outFilePath).toString() : null; + + return results.concat(test(currTarget, outFile)); + } + + return results; +} + +/** + * Read a test header yaml and transform it into an Object. + * + * @param header + * @returns {TestHeader} + */ +function readHeader(header: string): TestHeader { + const parsed: any = yaml.load(header); + + if (!parsed || parsed.constructor !== Object) { + throw "Unable to read test expectations: " + header; + } + + return { + expectation: parsed.namespace, + namespace: parsed.expectation, + }; +} diff --git a/wasm/tests/tsconfig.json b/wasm/tests/tsconfig.json new file mode 100644 index 0000000000..5f932585ba --- /dev/null +++ b/wasm/tests/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es2019", + "allowJs": true, + "lib": ["ES2019"], + "outDir": "dist", + "sourceMap": true, + "strict": true + }, + "exclude": ["node_modules", ".vscode-test"], + "include": ["./src/**/*"] +}