1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228
|
open Test_framework
open Test_helpers
(** Comprehensive Project parser error tests
These tests systematically verify error handling for:
- Required field validation
- Field syntax errors
- Block syntax errors
- Value format errors
- Semantic validation errors *)
(** {1 Required Field Tests} *)
let test_missing_name () =
assert_project_parse_error ~content:"version: 1.0.0\nobuild-ver: 1\n"
~expected_msg:"Missing required field: name" ~name:"missing name field"
let test_missing_version () =
assert_project_parse_error ~content:"name: test\nobuild-ver: 1\n"
~expected_msg:"Missing required field: version" ~name:"missing version field"
let test_missing_obuild_ver () =
assert_project_parse_error ~content:"name: test\nversion: 1.0.0\n"
~expected_msg:"Missing required field: obuild-ver" ~name:"missing obuild-ver field"
let test_empty_name () =
assert_project_parse_error ~content:"name:\nversion: 1.0.0\nobuild-ver: 1\n"
~expected_msg:"Missing required field: name" ~name:"empty name value"
(** {1 Field Value Tests} *)
let test_invalid_obuild_ver () =
assert_project_parse_error ~content:"name: test\nversion: 1.0.0\nobuild-ver: not_a_number\n"
~expected_msg:"int_of_string" ~name:"invalid obuild-ver value"
let test_future_obuild_ver () =
assert_project_parse_error ~content:"name: test\nversion: 1.0.0\nobuild-ver: 999\n"
~expected_msg:"Unsupported obuild version" ~name:"unsupported future obuild-ver"
let test_valid_minimal () =
assert_project_parses ~content:minimal_project ~name:"valid minimal project"
(** {1 Block Section Tests} *)
(* Note: "library: value" is lexed as KEY_VALUE, not BLOCK (because of the colon).
Since "library" isn't a recognized top-level field, it's silently ignored.
No library is created, so parsing succeeds with just the project metadata. *)
let test_block_as_value () =
assert_project_parses ~content:"name: test\nversion: 1.0.0\nobuild-ver: 1\nlibrary: some_value\n"
~name:"library: value is ignored (KEY_VALUE not BLOCK)"
let test_executable_block_as_value () =
(* Same as above - "executable: value" is KEY_VALUE, silently ignored *)
assert_project_parses
~content:"name: test\nversion: 1.0.0\nobuild-ver: 1\nexecutable: some_value\n"
~name:"executable: value is ignored (KEY_VALUE not BLOCK)"
(** {1 Library Block Tests} *)
let test_library_without_modules () =
(* Library with no modules fails at validation *)
assert_project_parse_error
~content:"name: test\nversion: 1.0.0\nobuild-ver: 1\n\nlibrary mylib\n src-dir: src\n"
~expected_msg:"has no modules" ~name:"library without modules"
let test_valid_library () =
(* Parser validates that modules exist on disk - this will fail without actual files *)
assert_project_parse_error
~content:"name: test\nversion: 1.0.0\nobuild-ver: 1\n\nlibrary mylib\n modules: Foo, Bar\n src-dir: src\n"
~expected_msg:"ModuleNotFound" ~name:"library with non-existent modules"
(** {1 Executable Block Tests} *)
let test_executable_without_name () =
(* New parser allows empty name but validation catches missing main file *)
assert_project_parse_error
~content:"name: test\nversion: 1.0.0\nobuild-ver: 1\n\nexecutable\n main-is: main.ml\n"
~expected_msg:"FileNotFoundInPaths" ~name:"executable without name"
let test_valid_executable () =
(* Parser validates that main-is file exists on disk *)
assert_project_parse_error
~content:"name: test\nversion: 1.0.0\nobuild-ver: 1\n\nexecutable myexe\n main-is: main.ml\n src-dir: src\n"
~expected_msg:"FileNotFoundInPaths" ~name:"executable with non-existent main file"
(** {1 Test Block Tests} *)
let test_test_without_name () =
(* New parser allows empty test name; Project.check() doesn't validate tests *)
assert_project_parses
~content:"name: test\nversion: 1.0.0\nobuild-ver: 1\n\ntest\n main-is: test.ml\n"
~name:"test without name (allowed)"
let test_valid_test () =
assert_project_parses
~content:"name: test\nversion: 1.0.0\nobuild-ver: 1\n\ntest mytest\n main-is: test.ml\n src-dir: tests\n"
~name:"valid test block"
(** {1 Field Format Tests} *)
let test_multiline_description () =
assert_project_parses
~content:
"name: test\n\
version: 1.0.0\n\
obuild-ver: 1\n\
description: This is a long\n\
description that spans\n\
multiple lines\n"
~name:"multiline description"
let test_csv_authors () =
assert_project_parses
~content:
"name: test\n\
version: 1.0.0\n\
obuild-ver: 1\n\
authors: Alice <alice@example.com>, Bob <bob@example.com>\n"
~name:"CSV authors field"
let test_single_author () =
assert_project_parses
~content:"name: test\nversion: 1.0.0\nobuild-ver: 1\nauthor: Alice <alice@example.com>\n"
~name:"single author field"
(** {1 Unknown Field Handling} *)
let test_unknown_field_strict () =
(* In strict mode, unknown fields should cause errors *)
(* This test documents current behavior *)
assert_project_parses ~content:"name: test\nversion: 1.0.0\nobuild-ver: 1\nunknown-field: value\n"
~name:"unknown field in non-strict mode"
(** {1 Indentation and Whitespace} *)
let test_empty_file () =
assert_project_parse_error ~content:"" ~expected_msg:"Missing required field: name"
~name:"empty file"
let test_whitespace_only () =
assert_project_parse_error ~content:" \n \n " ~expected_msg:"Missing required field: name"
~name:"whitespace only"
let test_valid_with_comments () =
assert_project_parses ~content:"# This is a comment\nname: test\nversion: 1.0.0\nobuild-ver: 1\n"
~name:"file with comments"
(** {1 Complex Nested Structures} *)
let test_multiple_libraries () =
(* Parser validates module existence *)
assert_project_parse_error
~content:"name: test\nversion: 1.0.0\nobuild-ver: 1\n\nlibrary lib1\n modules: Foo\n src-dir: src1\n\nlibrary lib2\n modules: Bar\n src-dir: src2\n"
~expected_msg:"ModuleNotFound" ~name:"multiple libraries with non-existent modules"
let test_mixed_targets () =
(* Parser validates file/module existence *)
assert_project_parse_error
~content:"name: test\nversion: 1.0.0\nobuild-ver: 1\n\nlibrary mylib\n modules: Lib\n src-dir: lib\n\nexecutable myexe\n main-is: main.ml\n src-dir: src\n build-deps: mylib\n\ntest mytest\n main-is: test.ml\n src-dir: tests\n build-deps: mylib\n"
~expected_msg:"ModuleNotFound" ~name:"mixed targets with non-existent files"
(** {1 Edge Cases} *)
let test_library_too_many_names () =
(* New parser takes first name, ignores rest; fails on module validation *)
assert_project_parse_error
~content:"name: test\nversion: 1.0.0\nobuild-ver: 1\n\nlibrary lib1 lib2\n modules: Foo\n"
~expected_msg:"ModuleNotFound" ~name:"library with extra names (first used)"
let test_colons_vs_equals () =
(* Test both : and = syntax *)
assert_project_parses ~content:"name: test\nversion: 1.0.0\nobuild-ver: 1\n" ~name:"colon syntax"
(** {1 Real-World Examples} *)
let test_real_world_obuild () =
(* Based on actual obuild.obuild structure - but modules don't exist *)
assert_project_parse_error
~content:"name: example\nversion: 0.1.0\nsynopsis: Example project\nobuild-ver: 1\nlicense: BSD\nauthors: Test Author <test@example.com>\n\nlibrary example_lib\n modules: Foo, Bar\n src-dir: lib\n build-deps: unix\n\nexecutable example_exe\n main-is: main.ml\n src-dir: src\n build-deps: example_lib\n\ntest example_test\n main-is: test.ml\n src-dir: tests\n build-deps: example_lib\n"
~expected_msg:"ModuleNotFound" ~name:"real-world obuild file with non-existent files"
(** {1 Test Suite} *)
let all_tests =
[
(* Required fields *)
make_test "missing_name" test_missing_name;
make_test "missing_version" test_missing_version;
make_test "missing_obuild_ver" test_missing_obuild_ver;
make_test "empty_name" test_empty_name;
(* Field values *)
make_test "invalid_obuild_ver" test_invalid_obuild_ver;
make_test "future_obuild_ver" test_future_obuild_ver;
make_test "valid_minimal" test_valid_minimal;
(* Block errors *)
make_test "block_as_value" test_block_as_value;
make_test "executable_block_as_value" test_executable_block_as_value;
(* Library blocks *)
make_test "library_without_modules" test_library_without_modules;
make_test "library_non_existent_modules" test_valid_library;
(* Executable blocks *)
make_test "executable_without_name" test_executable_without_name;
make_test "executable_non_existent_main" test_valid_executable;
(* Test blocks *)
make_test "test_without_name" test_test_without_name;
make_test "valid_test" test_valid_test;
(* Field formats *)
make_test "multiline_description" test_multiline_description;
make_test "csv_authors" test_csv_authors;
make_test "single_author" test_single_author;
(* Unknown fields *)
make_test "unknown_field_strict" test_unknown_field_strict;
(* Whitespace *)
make_test "empty_file" test_empty_file;
make_test "whitespace_only" test_whitespace_only;
make_test "valid_with_comments" test_valid_with_comments;
(* Complex structures *)
make_test "multiple_libraries_non_existent" test_multiple_libraries;
make_test "mixed_targets_non_existent" test_mixed_targets;
(* Edge cases *)
make_test "library_too_many_names" test_library_too_many_names;
make_test "colons_vs_equals" test_colons_vs_equals;
(* Real-world *)
make_test "real_world_non_existent_files" test_real_world_obuild;
]
let () = run_tests all_tests
|