diff --git a/.github/workflows/nightly_check.yml b/.github/workflows/nightly_check.yml index 13caaa138..1af3a1a12 100644 --- a/.github/workflows/nightly_check.yml +++ b/.github/workflows/nightly_check.yml @@ -52,3 +52,21 @@ jobs: - name: moon test run: | moon test --target all + + - name: checkout openseek corpus + uses: actions/checkout@v4 + with: + repository: moonbitlang/openseek + ref: main + path: .openseek + fetch-depth: 1 + submodules: recursive + persist-credentials: false + + - name: test openseek corpus + env: + OPENSEEK_ROOT: ${{ github.workspace }}/.openseek + run: | + echo "OpenSeek commit: $(git -C "$OPENSEEK_ROOT" rev-parse HEAD)" + git -C "$OPENSEEK_ROOT" submodule status --recursive + moon test test/openseek_test/openseek_test.mbt --target native --include-skipped diff --git a/.github/workflows/stable_check.yml b/.github/workflows/stable_check.yml index 918e962da..b42b3ceb3 100644 --- a/.github/workflows/stable_check.yml +++ b/.github/workflows/stable_check.yml @@ -49,3 +49,21 @@ jobs: - name: moon test run: | moon test --target all + + - name: checkout openseek corpus + uses: actions/checkout@v4 + with: + repository: moonbitlang/openseek + ref: main + path: .openseek + fetch-depth: 1 + submodules: recursive + persist-credentials: false + + - name: test openseek corpus + env: + OPENSEEK_ROOT: ${{ github.workspace }}/.openseek + run: | + echo "OpenSeek commit: $(git -C "$OPENSEEK_ROOT" rev-parse HEAD)" + git -C "$OPENSEEK_ROOT" submodule status --recursive + moon test test/openseek_test/openseek_test.mbt --target native --include-skipped diff --git a/test/openseek_test/moon.pkg b/test/openseek_test/moon.pkg new file mode 100644 index 000000000..af20eaf3b --- /dev/null +++ b/test/openseek_test/moon.pkg @@ -0,0 +1,7 @@ +import { + "moonbitlang/parser", + "moonbitlang/parser/untyped_cst", + "moonbitlang/lexer/basic", + "moonbitlang/x/fs", + "moonbitlang/core/env", +} for "test" diff --git a/test/openseek_test/openseek_test.mbt b/test/openseek_test/openseek_test.mbt new file mode 100644 index 000000000..5c1ca2be9 --- /dev/null +++ b/test/openseek_test/openseek_test.mbt @@ -0,0 +1,115 @@ +///| +fn join_path(dir : String, entry : String) -> String { + if dir.has_suffix("/") { + dir + entry + } else { + dir + "/" + entry + } +} + +///| +fn collect_mbt_files(dir : String, files : Array[String]) -> Unit raise Error { + let entries = @fs.read_dir(dir) catch { + err => fail("failed to read directory \{dir}: \{Repr(err)}") + } + for entry in entries { + if entry != ".git" { + let path = join_path(dir, entry) + if !@fs.path_exists(path) { + if entry.has_suffix(".mbt") { + files.push(path) + } + continue + } + let is_dir = @fs.is_dir(path) catch { + err => fail("failed to inspect \{path}: \{Repr(err)}") + } + if !is_dir && entry.has_suffix(".mbt") { + files.push(path) + } else if is_dir { + collect_mbt_files(path, files) + } + } + } +} + +///| +fn reports_from_view(view : ArrayView[@basic.Report]) -> Array[@basic.Report] { + let reports : Array[@basic.Report] = [] + for report in view { + reports.push(report) + } + reports +} + +///| +fn record_diagnostics( + failures : Array[String], + path : String, + parser : String, + reports : Array[@basic.Report], +) -> Unit { + if !reports.is_empty() { + let rendered = @basic.show_loc.protect(String, fn() { + reports.to_json().stringify(indent=2) + }) + failures.push("\{path}\nparser: \{parser}\ndiagnostics:\n\{rendered}") + } +} + +///| +#skip("requires an external openseek checkout") +test "all OpenSeek MoonBit sources parse without diagnostics" { + let root = @env.get_env_var("OPENSEEK_ROOT").unwrap_or_else(() => { + fail("OPENSEEK_ROOT must point to an OpenSeek checkout") + }) + if root.is_empty() { + fail("OPENSEEK_ROOT must not be empty") + } + let root_is_dir = @fs.is_dir(root) catch { + err => fail("failed to inspect OPENSEEK_ROOT \{root}: \{Repr(err)}") + } + if !root_is_dir { + fail("OPENSEEK_ROOT is not a directory: \{root}") + } + let files : Array[String] = [] + collect_mbt_files(root, files) + files.sort() + if files.is_empty() { + fail("no .mbt files found under OPENSEEK_ROOT: \{root}") + } + let failures : Array[String] = [] + for path in files { + let source = @fs.read_file_to_string(path) catch { + err => fail("failed to read \{path}: \{Repr(err)}") + } + let (_, handrolled_reports) = @parser.parse_string( + source, + name=path, + parser=Handrolled, + ) + record_diagnostics(failures, path, "Handrolled", handrolled_reports) + let (_, moonyacc_reports) = @parser.parse_string( + source, + name=path, + parser=MoonYacc, + ) + record_diagnostics(failures, path, "MoonYacc", moonyacc_reports) + let cst = @untyped_cst.parse_structure(source, name=path) + record_diagnostics( + failures, + path, + "untyped CST", + reports_from_view(cst.diagnostics_view()), + ) + let (_, lowering_reports) = cst.to_impls() + record_diagnostics(failures, path, "untyped CST to_impls", lowering_reports) + } + if !failures.is_empty() { + fail( + "OpenSeek corpus parsing failed for \{failures.length()} parser/file checks:\n\n" + + failures.join("\n\n"), + ) + } + println("parsed \{files.length()} OpenSeek .mbt files with all three parsers") +} diff --git a/test/openseek_test/pkg.generated.mbti b/test/openseek_test/pkg.generated.mbti new file mode 100644 index 000000000..3e2e00b57 --- /dev/null +++ b/test/openseek_test/pkg.generated.mbti @@ -0,0 +1,12 @@ +// Generated using `moon info`, DON'T EDIT IT +package "moonbitlang/parser/test/openseek_test" + +// Values + +// Errors + +// Types and methods + +// Type aliases + +// Traits