commit ab055ed8fc04cb437d46022cdd48ad1493a97669 Author: JOLIMAITRE Matthieu Date: Fri Jan 20 08:29:23 2023 +0100 init diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..aa1c94e --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "deno.enable": true, + "deno.unstable": true +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..8915b08 --- /dev/null +++ b/README.md @@ -0,0 +1,27 @@ +# CAMELO + +Because we can't anymore :( + +--- + +## Description + +Script to run ocaml test suites on Caml code even if it does not compile. + +## Usage + +requires [deno](https://deno.land). + +```sh +deno run -A --unstable "./run.ts" +``` + +example : + +```sh +deno run -A --unstable "./run.ts" "./example/source.ml" "./example/tests" +``` + +## Author + +- JOLIMAITRE Matthieu diff --git a/example/source.ml b/example/source.ml new file mode 100644 index 0000000..f6dc66b --- /dev/null +++ b/example/source.ml @@ -0,0 +1,5 @@ +let add a b = a + b;; + +let failure = a+ b;; + +add 3 4;; diff --git a/example/tests/add_1.ml b/example/tests/add_1.ml new file mode 100644 index 0000000..a58d622 --- /dev/null +++ b/example/tests/add_1.ml @@ -0,0 +1,2 @@ +let r = add 1 2;; +assert (r = 3);; diff --git a/example/tests/add_2.ml b/example/tests/add_2.ml new file mode 100644 index 0000000..fc98840 --- /dev/null +++ b/example/tests/add_2.ml @@ -0,0 +1,2 @@ +let r = add 1 2;; +assert (r = 4);; diff --git a/example/tests/add_3.ml b/example/tests/add_3.ml new file mode 100644 index 0000000..118b2ec --- /dev/null +++ b/example/tests/add_3.ml @@ -0,0 +1,2 @@ +let r = add_ 1 2;; +assert (r = 3);; diff --git a/lib/source.ts b/lib/source.ts new file mode 100644 index 0000000..4802d26 --- /dev/null +++ b/lib/source.ts @@ -0,0 +1,50 @@ +import { decode, show_diff } from "./utils.ts"; + +export type RunRes = { + success: true; + output: string; +} | { + success: false; + error: string; +}; + +export async function run_script(script: string): Promise { + const tmp_path = "/tmp/script.ml"; + await Deno.writeTextFile(tmp_path, script); + const process = Deno.run({ + cmd: ["ocaml", tmp_path], + stdout: "piped", + stderr: "piped", + }); + const { success } = await process.status(); + await Deno.remove(tmp_path); + + if (success) { + return { success, output: decode(await process.output()) }; + } else { + return { success, error: decode(await process.stderrOutput()) }; + } +} + +function remove_comments(source: string): string { + const [head, ...rest] = source.split("(*"); + const remainder = rest.map((w) => { + const [_, code] = w.split("*)"); + return code; + }); + return [head, ...remainder].join(""); +} + +export async function reduce_to_runable(source: string) { + const clean_source = remove_comments(source); + let current = ""; + for (const part of clean_source.split(";;")) { + let concatenated; + if (current == "") concatenated = part; + else concatenated = `${current};;${part}`; + const res = await run_script(concatenated); + if (res.success) current = concatenated; + } + await show_diff(clean_source, current); + return current; +} diff --git a/lib/test.ts b/lib/test.ts new file mode 100644 index 0000000..1be59cd --- /dev/null +++ b/lib/test.ts @@ -0,0 +1,49 @@ +import col from "https://deno.land/x/chalkin@v0.1.3/mod.ts"; +import { run_script } from "./source.ts"; + +export type Test = { name: string; content: string }; + +export async function find_tests(dir: string): Promise { + const result = [] as Test[]; + for await (const { name } of Deno.readDir(dir)) { + const content = await Deno.readTextFile(`${dir}/${name}`); + result.push({ content, name }); + } + result.sort((a, b) => a.name > b.name ? 1 : -1); + return result; +} + +export type TestRes = { + test: Test; + success: true; +} | { + test: Test; + success: false; + error: string; +}; + +export async function run_test( + test: Test, + prelude: string, +): Promise { + const source = `${prelude}\n${test.content}`; + const res = await run_script(source); + const { success } = res; + if (success) return { success, test }; + const { error } = res; + return { success, error, test }; +} + +export function display_test_success({ success, test: { name } }: TestRes) { + console.log(`[${success ? col.green("O") : col.red("X")}] ${name}`); +} + +export function display_test_failures(results: TestRes[]) { + for (const result of results) { + const { test: { name }, success } = result; + if (success) continue; + console.log(`${col.red(`---- error for '${name}' ----`)} +${result.error} +`); + } +} diff --git a/lib/utils.ts b/lib/utils.ts new file mode 100644 index 0000000..c08fdee --- /dev/null +++ b/lib/utils.ts @@ -0,0 +1,12 @@ +export function decode(input: Uint8Array): string { + return new TextDecoder().decode(input); +} + +export async function show_diff(a: string, b: string) { + const [path_a, path_b] = ["/tmp/diff_a", "/tmp/diff_b"]; + await Deno.writeTextFile(path_a, a); + await Deno.writeTextFile(path_b, b); + await Deno.run({ cmd: ["diff", path_a, path_b] }).status(); + await Deno.remove(path_a); + await Deno.remove(path_b); +} diff --git a/mod.ts b/mod.ts new file mode 100644 index 0000000..e69de29 diff --git a/run.ts b/run.ts new file mode 100644 index 0000000..64b7459 --- /dev/null +++ b/run.ts @@ -0,0 +1,44 @@ +import col from "https://deno.land/x/chalkin@v0.1.3/mod.ts"; +import { Cli } from "https://deno.land/x/clir@v1.1.0/mod.ts"; +import { reduce_to_runable } from "./lib/source.ts"; +import { + display_test_failures, + display_test_success, + find_tests, + run_test, + TestRes, +} from "./lib/test.ts"; + +const cli = new Cli({ + name: "camelo", + description: "tool to write and run caml test suites fast", + parameters: { + "input": { + description: + 'input files to use before testsuites separated by comas\nex: "file1.ml,file2.ml"', + }, + "tests": { + description: "directory containing tests as ocaml files", + }, + }, +}); + +const input_files = cli.parameter_value("input")!.split(","); +const raw_sources = await Promise.all( + input_files.map((f) => Deno.readTextFile(f)), +); +console.log(col.blue("---- diff to runnable sections ----")); +const sources = await Promise.all(raw_sources.map(reduce_to_runable)); +const prelude = sources.join("\n"); + +const tests_dir = cli.parameter_value("tests")!; +const tests = await find_tests(tests_dir); + +console.log(col.blue("---- running tests ----")); +const results = [] as TestRes[]; +for (const test of tests) { + const res = await run_test(test, prelude); + display_test_success(res); + results.push(res); +} +display_test_failures(results);