init
This commit is contained in:
commit
ab055ed8fc
11 changed files with 197 additions and 0 deletions
4
.vscode/settings.json
vendored
Normal file
4
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"deno.enable": true,
|
||||||
|
"deno.unstable": true
|
||||||
|
}
|
27
README.md
Normal file
27
README.md
Normal file
|
@ -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" <input_files> <tests_folder>
|
||||||
|
```
|
||||||
|
|
||||||
|
example :
|
||||||
|
|
||||||
|
```sh
|
||||||
|
deno run -A --unstable "./run.ts" "./example/source.ml" "./example/tests"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Author
|
||||||
|
|
||||||
|
- JOLIMAITRE Matthieu <matthieu@imagevo.fr>
|
5
example/source.ml
Normal file
5
example/source.ml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
let add a b = a + b;;
|
||||||
|
|
||||||
|
let failure = a+ b;;
|
||||||
|
|
||||||
|
add 3 4;;
|
2
example/tests/add_1.ml
Normal file
2
example/tests/add_1.ml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
let r = add 1 2;;
|
||||||
|
assert (r = 3);;
|
2
example/tests/add_2.ml
Normal file
2
example/tests/add_2.ml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
let r = add 1 2;;
|
||||||
|
assert (r = 4);;
|
2
example/tests/add_3.ml
Normal file
2
example/tests/add_3.ml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
let r = add_ 1 2;;
|
||||||
|
assert (r = 3);;
|
50
lib/source.ts
Normal file
50
lib/source.ts
Normal file
|
@ -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<RunRes> {
|
||||||
|
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;
|
||||||
|
}
|
49
lib/test.ts
Normal file
49
lib/test.ts
Normal file
|
@ -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<Test[]> {
|
||||||
|
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<TestRes> {
|
||||||
|
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}
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
}
|
12
lib/utils.ts
Normal file
12
lib/utils.ts
Normal file
|
@ -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);
|
||||||
|
}
|
0
mod.ts
Normal file
0
mod.ts
Normal file
44
run.ts
Normal file
44
run.ts
Normal file
|
@ -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);
|
Loading…
Add table
Add a link
Reference in a new issue