414 lines
11 KiB
Rust
414 lines
11 KiB
Rust
use std::{
|
|
ffi::OsStr,
|
|
fs,
|
|
path::{Path, PathBuf},
|
|
process::{Command, Output},
|
|
sync::atomic::{AtomicUsize, Ordering},
|
|
};
|
|
|
|
static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
|
|
|
|
struct WorkspacePackageSpec<'a> {
|
|
member: &'a str,
|
|
manifest: &'a str,
|
|
modules: &'a [(&'a str, &'a str)],
|
|
}
|
|
|
|
#[test]
|
|
fn beta_project_proves_general_purpose_cli_surface() {
|
|
let project = unique_path("beta-1-0-0-project");
|
|
let std_path = slovo_std_path();
|
|
|
|
let scaffold = run_glagol(["new".as_ref(), project.as_os_str()]);
|
|
assert_success("beta scaffold", &scaffold);
|
|
|
|
fs::write(
|
|
project.join("src/model.slo"),
|
|
r#"(module model (export Mode Report make_report total_score mode_name second_label report_name report_mode_text))
|
|
|
|
(import std.vec_i32 (pair sum))
|
|
|
|
(import std.num (i64_to_string))
|
|
|
|
(enum Mode
|
|
Dev
|
|
(Code i64))
|
|
|
|
(struct Report
|
|
(name string)
|
|
(scores (vec i32))
|
|
(ratio f64)
|
|
(active bool)
|
|
(mode Mode)
|
|
(labels (array string 2)))
|
|
|
|
(fn make_report () -> Report
|
|
(Report (name "beta") (scores (pair 3 4)) (ratio 1.5) (active true) (mode (Mode.Code 7i64)) (labels (array string "slovo" "glagol"))))
|
|
|
|
(fn total_score ((report Report)) -> i32
|
|
(sum (. report scores)))
|
|
|
|
(fn mode_name ((mode Mode)) -> string
|
|
(match mode
|
|
((Mode.Dev)
|
|
"dev")
|
|
((Mode.Code payload)
|
|
(i64_to_string payload))))
|
|
|
|
(fn second_label ((report Report)) -> string
|
|
(index (. report labels) 1))
|
|
|
|
(fn report_name () -> string
|
|
(. (make_report) name))
|
|
|
|
(fn report_mode_text () -> string
|
|
(mode_name (. (make_report) mode)))
|
|
"#,
|
|
)
|
|
.expect("write beta model");
|
|
|
|
fs::write(
|
|
project.join("src/helpers.slo"),
|
|
r#"(module helpers (export title_text parse_port_or port_parses big_counter_text parse_ratio parse_enabled))
|
|
|
|
(import std.string (concat parse_u32_result parse_u32_or parse_f64_or parse_bool_or))
|
|
|
|
(import std.num (u64_to_string))
|
|
|
|
(import std.result (is_ok_u32))
|
|
|
|
(fn title_text ((left string) (right string)) -> string
|
|
(concat left right))
|
|
|
|
(fn parse_port_or ((text string) (fallback u32)) -> u32
|
|
(parse_u32_or text fallback))
|
|
|
|
(fn port_parses ((text string)) -> bool
|
|
(is_ok_u32 (parse_u32_result text)))
|
|
|
|
(fn big_counter_text ((value u64)) -> string
|
|
(u64_to_string value))
|
|
|
|
(fn parse_ratio ((text string) (fallback f64)) -> f64
|
|
(parse_f64_or text fallback))
|
|
|
|
(fn parse_enabled ((text string) (fallback bool)) -> bool
|
|
(parse_bool_or text fallback))
|
|
"#,
|
|
)
|
|
.expect("write beta helpers");
|
|
|
|
fs::write(
|
|
project.join("src/main.slo"),
|
|
r#"(module main)
|
|
|
|
(import model (make_report total_score second_label report_name report_mode_text))
|
|
|
|
(import helpers (title_text parse_port_or port_parses big_counter_text parse_ratio parse_enabled))
|
|
|
|
(fn loop_sum ((limit i32)) -> i32
|
|
(var current i32 0)
|
|
(var total i32 0)
|
|
(while (< current limit)
|
|
(set total (+ total current))
|
|
(set current (+ current 1)))
|
|
total)
|
|
|
|
(fn main () -> i32
|
|
(std.io.print_string (title_text (report_name) "!"))
|
|
0)
|
|
|
|
(test "beta project string helpers"
|
|
(= (title_text "slo" "vo") "slovo"))
|
|
|
|
(test "beta project unsigned helpers"
|
|
(if (= (parse_port_or "8080" 1u32) 8080u32)
|
|
(if (port_parses "8080")
|
|
(= (big_counter_text 42u64) "42")
|
|
false)
|
|
false))
|
|
|
|
(test "beta project float bool helpers"
|
|
(if (= (parse_ratio "3.5" 0.0) 3.5)
|
|
(parse_enabled "true" false)
|
|
false))
|
|
|
|
(test "beta project data types"
|
|
(if (= (total_score (make_report)) 7)
|
|
(if (= (report_mode_text) "7")
|
|
(= (second_label (make_report)) "glagol")
|
|
false)
|
|
false))
|
|
|
|
(test "beta project control flow"
|
|
(= (loop_sum 5) 10))
|
|
"#,
|
|
)
|
|
.expect("write beta main");
|
|
|
|
let check = run_glagol_with_env(
|
|
["check".as_ref(), project.as_os_str()],
|
|
&[("SLOVO_STD_PATH", std_path.as_os_str())],
|
|
);
|
|
assert_success("beta project check", &check);
|
|
|
|
let fmt_check = run_glagol_with_env(
|
|
["fmt".as_ref(), "--check".as_ref(), project.as_os_str()],
|
|
&[("SLOVO_STD_PATH", std_path.as_os_str())],
|
|
);
|
|
assert_success("beta project fmt --check", &fmt_check);
|
|
|
|
let test = run_glagol_with_env(
|
|
["test".as_ref(), project.as_os_str()],
|
|
&[("SLOVO_STD_PATH", std_path.as_os_str())],
|
|
);
|
|
assert_success("beta project test", &test);
|
|
assert_eq!(
|
|
String::from_utf8_lossy(&test.stdout),
|
|
"test \"beta project string helpers\" ... ok\n\
|
|
test \"beta project unsigned helpers\" ... ok\n\
|
|
test \"beta project float bool helpers\" ... ok\n\
|
|
test \"beta project data types\" ... ok\n\
|
|
test \"beta project control flow\" ... ok\n\
|
|
5 test(s) passed\n"
|
|
);
|
|
|
|
let docs = unique_path("beta-1-0-0-project-docs");
|
|
let doc = run_glagol_with_env(
|
|
[
|
|
"doc".as_ref(),
|
|
project.as_os_str(),
|
|
"-o".as_ref(),
|
|
docs.as_os_str(),
|
|
],
|
|
&[("SLOVO_STD_PATH", std_path.as_os_str())],
|
|
);
|
|
assert_success("beta project doc", &doc);
|
|
let doc_index = fs::read_to_string(docs.join("index.md")).expect("read beta docs");
|
|
assert!(doc_index.contains("# Project "));
|
|
assert!(doc_index.contains("## Module model"));
|
|
assert!(doc_index.contains("## Module helpers"));
|
|
assert!(doc_index.contains("## Module main"));
|
|
assert!(doc_index.contains("beta project control flow"));
|
|
|
|
let binary = unique_path("beta-1-0-0-project-bin");
|
|
let build = run_glagol_with_env(
|
|
[
|
|
"build".as_ref(),
|
|
project.as_os_str(),
|
|
"-o".as_ref(),
|
|
binary.as_os_str(),
|
|
],
|
|
&[("SLOVO_STD_PATH", std_path.as_os_str())],
|
|
);
|
|
if build.status.success() {
|
|
let run = Command::new(&binary).output().expect("run beta project");
|
|
assert_success("beta project binary", &run);
|
|
assert_eq!(String::from_utf8_lossy(&run.stdout), "beta!\n");
|
|
} else {
|
|
assert_stderr_contains("beta project build", &build, "ToolchainUnavailable");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn beta_workspace_proves_local_package_dependency_flow() {
|
|
let workspace = write_workspace(
|
|
"beta-1-0-0-workspace",
|
|
"[workspace]\nmembers = [\"packages/app\", \"packages/libutil\"]\n",
|
|
&[
|
|
WorkspacePackageSpec {
|
|
member: "packages/libutil",
|
|
manifest: "[package]\nname = \"libutil\"\nversion = \"0.1.0\"\n",
|
|
modules: &[(
|
|
"text",
|
|
r#"(module text (export Status stamp parse_count render_total))
|
|
|
|
(import std.string (concat parse_u64_or))
|
|
|
|
(import std.num (u64_to_string))
|
|
|
|
(enum Status
|
|
Ready
|
|
Busy)
|
|
|
|
(fn stamp ((status Status)) -> string
|
|
(match status
|
|
((Status.Ready)
|
|
"ready")
|
|
((Status.Busy)
|
|
"busy")))
|
|
|
|
(fn parse_count ((text string)) -> u64
|
|
(parse_u64_or text 0u64))
|
|
|
|
(fn render_total ((label string) (value u64)) -> string
|
|
(concat label (u64_to_string value)))
|
|
"#,
|
|
)],
|
|
},
|
|
WorkspacePackageSpec {
|
|
member: "packages/app",
|
|
manifest: "[package]\nname = \"app\"\nversion = \"0.1.0\"\n\n[dependencies]\nlibutil = { path = \"../libutil\" }\n",
|
|
modules: &[(
|
|
"main",
|
|
r#"(module main)
|
|
|
|
(import libutil.text (Status stamp parse_count render_total))
|
|
|
|
(fn main () -> i32
|
|
(if (= (parse_count "12") 12u64)
|
|
0
|
|
1))
|
|
|
|
(test "beta workspace package import"
|
|
(if (= (stamp (Status.Ready)) "ready")
|
|
(= (render_total "n=" 12u64) "n=12")
|
|
false))
|
|
"#,
|
|
)],
|
|
},
|
|
],
|
|
);
|
|
let std_path = slovo_std_path();
|
|
|
|
let check = run_glagol_with_env(
|
|
["check".as_ref(), workspace.as_os_str()],
|
|
&[("SLOVO_STD_PATH", std_path.as_os_str())],
|
|
);
|
|
assert_success_stdout("beta workspace check", check, "");
|
|
|
|
let test = run_glagol_with_env(
|
|
["test".as_ref(), workspace.as_os_str()],
|
|
&[("SLOVO_STD_PATH", std_path.as_os_str())],
|
|
);
|
|
assert_success_stdout(
|
|
"beta workspace test",
|
|
test,
|
|
"test \"beta workspace package import\" ... ok\n1 test(s) passed\n",
|
|
);
|
|
|
|
let binary = unique_path("beta-1-0-0-workspace-bin");
|
|
let build = run_glagol_with_env(
|
|
[
|
|
"build".as_ref(),
|
|
workspace.as_os_str(),
|
|
"-o".as_ref(),
|
|
binary.as_os_str(),
|
|
],
|
|
&[("SLOVO_STD_PATH", std_path.as_os_str())],
|
|
);
|
|
if build.status.success() {
|
|
let run = Command::new(&binary)
|
|
.output()
|
|
.expect("run beta workspace binary");
|
|
assert_success("beta workspace binary", &run);
|
|
assert!(run.stdout.is_empty(), "beta workspace binary wrote stdout");
|
|
} else {
|
|
assert_stderr_contains("beta workspace build", &build, "ToolchainUnavailable");
|
|
}
|
|
}
|
|
|
|
fn slovo_std_path() -> PathBuf {
|
|
Path::new(env!("CARGO_MANIFEST_DIR"))
|
|
.join("../lib/std")
|
|
.canonicalize()
|
|
.expect("canonicalize slovo std path")
|
|
}
|
|
|
|
fn write_workspace(
|
|
name: &str,
|
|
workspace_manifest: &str,
|
|
packages: &[WorkspacePackageSpec<'_>],
|
|
) -> PathBuf {
|
|
let root = unique_path(name);
|
|
fs::create_dir_all(&root).expect("create workspace root");
|
|
fs::write(root.join("slovo.toml"), workspace_manifest).expect("write workspace manifest");
|
|
for package in packages {
|
|
let package_root = root.join(package.member);
|
|
let src = package_root.join("src");
|
|
fs::create_dir_all(&src).expect("create package src");
|
|
fs::write(package_root.join("slovo.toml"), package.manifest)
|
|
.expect("write package manifest");
|
|
for (module, source) in package.modules {
|
|
fs::write(src.join(format!("{}.slo", module)), source).expect("write package module");
|
|
}
|
|
}
|
|
root
|
|
}
|
|
|
|
fn unique_path(name: &str) -> PathBuf {
|
|
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
|
|
let nanos = std::time::SystemTime::now()
|
|
.duration_since(std::time::UNIX_EPOCH)
|
|
.expect("system clock before UNIX_EPOCH")
|
|
.as_nanos();
|
|
std::env::temp_dir().join(format!(
|
|
"glagol-beta-1-0-0-{}-{}-{}-{}",
|
|
std::process::id(),
|
|
nanos,
|
|
id,
|
|
name
|
|
))
|
|
}
|
|
|
|
fn run_glagol<I, S>(args: I) -> Output
|
|
where
|
|
I: IntoIterator<Item = S>,
|
|
S: AsRef<OsStr>,
|
|
{
|
|
Command::new(env!("CARGO_BIN_EXE_glagol"))
|
|
.args(args)
|
|
.output()
|
|
.expect("run glagol")
|
|
}
|
|
|
|
fn run_glagol_with_env<I, S>(args: I, envs: &[(&str, &OsStr)]) -> Output
|
|
where
|
|
I: IntoIterator<Item = S>,
|
|
S: AsRef<OsStr>,
|
|
{
|
|
let mut command = Command::new(env!("CARGO_BIN_EXE_glagol"));
|
|
command.args(args);
|
|
for (key, value) in envs {
|
|
command.env(key, value);
|
|
}
|
|
command.output().expect("run glagol with env")
|
|
}
|
|
|
|
fn assert_success(context: &str, output: &Output) {
|
|
assert!(
|
|
output.status.success(),
|
|
"{} failed\nstdout:\n{}\nstderr:\n{}",
|
|
context,
|
|
String::from_utf8_lossy(&output.stdout),
|
|
String::from_utf8_lossy(&output.stderr)
|
|
);
|
|
}
|
|
|
|
fn assert_success_stdout(context: &str, output: Output, expected: &str) {
|
|
assert_success(context, &output);
|
|
assert_eq!(
|
|
String::from_utf8_lossy(&output.stdout),
|
|
expected,
|
|
"{} stdout mismatch",
|
|
context
|
|
);
|
|
assert!(
|
|
output.stderr.is_empty(),
|
|
"{} wrote stderr:\n{}",
|
|
context,
|
|
String::from_utf8_lossy(&output.stderr)
|
|
);
|
|
}
|
|
|
|
fn assert_stderr_contains(context: &str, output: &Output, needle: &str) {
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
assert!(
|
|
stderr.contains(needle),
|
|
"{} stderr did not contain `{}`:\n{}",
|
|
context,
|
|
needle,
|
|
stderr
|
|
);
|
|
}
|