/* Unit tests of tlverify.c Copyright © 2021-2024 Samuel Lidén Borell Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "../tlverify.c" #include "../hash.h" #include "unittest.h" #include "alltests.h" #include "testcommon.h" #include "parsecommon.h" #include #include static struct CSlul *ctx; static struct CSlulConfig *cfg; static int mhlines; #define MAINFILE 111U #define SRCFILE 222U #define SOMEIFACE 333U #define OTHERIFACE 444U #define SLULRTIFACE 555U struct MockedFile { const char *buffer; size_t remaining; }; static struct MockedFile mocked_mainfile; static struct MockedFile mocked_srcfile; static struct MockedFile mocked_someiface; static struct MockedFile mocked_otheriface; static struct MockedFile mocked_slulrtiface; static CSlulFile mocked_fopen(const char *name, const char *mode) { tsoftassert(!strcmp(mode, "rb")); if (!strcmp(name, "main.slul")) { return (CSlulFile)MAINFILE; } else if (!strcmp(name, "source.slul")) { return (CSlulFile)SRCFILE; } else if (!strcmp(name, "#interface dir#somelib.slul")) { return (CSlulFile)SOMEIFACE; } else if (!strcmp(name, "#interface dir#otherlib.slul")) { return (CSlulFile)OTHERIFACE; } else if (!strcmp(name, "#interface dir#slulrt.slul")) { return (CSlulFile)SLULRTIFACE; } else { tprintf("Incorrect file name: %s\n", name); tsoftfail("Incorrect file name"); return (CSlulFile)NULL; } } static CSlulFile mocked_createexec(const char *name) { (void)name; tsoftfail("Should not create files in CSLUL_OT_CHECK mode"); return (CSlulFile)NULL; } static int mocked_fclose(CSlulFile file) { tsoftassert((uintptr)file == MAINFILE || (uintptr)file == SRCFILE || (uintptr)file == SOMEIFACE || (uintptr)file == OTHERIFACE || (uintptr)file == SLULRTIFACE); return 0; } static int mocked_ferror(CSlulFile file) { tsoftassert((uintptr)file == MAINFILE || (uintptr)file == SRCFILE || (uintptr)file == SOMEIFACE || (uintptr)file == OTHERIFACE || (uintptr)file == SLULRTIFACE); return 0; } static int mocked_remove(const char *filename) { (void)filename; tsoftfail("Should not delete files in CSLUL_OT_CHECK mode"); return 0; } static size_t mocked_fread(void *buffer, size_t size, size_t nmemb, CSlulFile file) { struct MockedFile *fileinfo; size_t readsize; tassert(buffer != NULL); tassert(size == 1); switch ((uintptr)file) { case MAINFILE: fileinfo = &mocked_mainfile; break; case SRCFILE: fileinfo = &mocked_srcfile; break; case SOMEIFACE: fileinfo = &mocked_someiface; break; case OTHERIFACE: fileinfo = &mocked_otheriface; break; case SLULRTIFACE: fileinfo = &mocked_slulrtiface; break; default: tfail("Wrong file handle"); return 0; } readsize = (nmemb >= fileinfo->remaining ? fileinfo->remaining : nmemb); memcpy(buffer, fileinfo->buffer, readsize); fileinfo->buffer += readsize; fileinfo->remaining -= readsize; return readsize; } static int mocked_mkdir(const char *name) { (void)name; tsoftfail("Should not call mkdir in CSLUL_OT_CHECK mode"); return 0; } static void set_mocked_file(struct MockedFile *fileinfo, const char *s, size_t len) { fileinfo->buffer = s; fileinfo->remaining = len; } static void make_config(void) { static const struct CSlulInitParams prm = { sizeof(struct CSlulInitParams), '#', /* using # as dirsep */ NULL, NULL, NULL, /* malloc, free, realloc */ mocked_fopen, mocked_createexec, mocked_fclose, mocked_ferror, mocked_remove, NULL/* fwrite */, mocked_fread, mocked_mkdir, NULL/* dropprivs */ }; cfg = tcfg(cslul_config_create(&prm)); tassert(cfg != NULL); cslul_config_set_output_type(cfg, CSLUL_OT_CHECK); tassert(cslul_config_add_iface_dir(cfg, "#interface dir")); } static void make_ctx(void) { make_config(); ctx = create_ctx_cfg(CSLUL_P_INIT, cfg); tassert(ctx != NULL); } /** Runs before each test case */ static void before_test(void) { static const char slulrt_code[] = "\\slul 0.0.0\n" "\\name slulrt\n" "\\type libraryspec\n" "\\version 0.0.0\n" "\\api_def 0.0.0 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n" "\n"; ctx = NULL; cfg = NULL; num_expected = 0; errors_in(NULL); set_mocked_file(&mocked_slulrtiface, slulrt_code, strlen(slulrt_code)); } /** Runs after each test case (not called from OoM-test) */ static void after_test(void) { verify_errors(); } /** Called from the end of each test case */ static void cleanup(void) { if (ctx) cslul_free(ctx); if (cfg) cslul_config_free(cfg); } static void single_module_test(int srcline, enum CSlulModuleType modtype, const char *iface, const char *impl) { static const char mh_app[] = "\\slul 0.0.0\n" "\\name app\n" "\\version 1.0.1\n" "\\source source.slul\n" "\n"; static const char mh_library[] = "\\slul 0.0.0\n" "\\name lib\n" "\\type library\n" "\\version 1.0.2\n" "\\api_def 1.0.0\n" "\\api_def 1.0.1\n" "\\api_def 1.2\n" "\\api_def 1.0.2\n" "\\source source.slul\n" "\n"; /* Construct the main.slul file */ const char *mh = (modtype == CSLUL_MT_APP ? mh_app : mh_library); size_t mhlen = strlen(mh); size_t ifacelen = iface ? strlen(iface) : 0; char *mainfile = tmalloc(mhlen+ifacelen); assert(mainfile != NULL); memcpy(mainfile, mh, mhlen); if (ifacelen) { const char *cp; memcpy(mainfile+mhlen, iface, ifacelen); mhlines = 0; for (cp = mainfile; cp < mainfile+mhlen; cp++) { if (*cp == '\n') mhlines++; } } sourceline = srcline; set_mocked_file(&mocked_mainfile, mainfile, mhlen+ifacelen); set_mocked_file(&mocked_srcfile, impl, strlen(impl)); make_ctx(); cslul_build(ctx, NULL); tfree(mainfile); } static void test_idents_internal(void) { struct TreeNode *tn; single_module_test(__LINE__, CSLUL_MT_APP, /* Interface */ NULL, /* Implementation */ "type InternalType = int\n" "data [3]byte someconst = [1,2,3]\n" "func somefunc(int x) { }\n"); tassert(!ctx->has_fatal_errors); tsoftassert(ctx->module.iface.types_list == NULL); tsoftassert(ctx->module.iface.types_root == NULL); tsoftassert(ctx->module.iface.idents_list == NULL); tsoftassert(ctx->module.iface.idents_root == NULL); /* Check "InternalType" */ tassert(ctx->impl.types_list != NULL); tassert(ctx->impl.types_root != NULL); tsoftassert(&ctx->impl.types_list->decl.ident == ctx->impl.types_root); tsoftassert(ctx->impl.types_list->next == NULL); tassert(ctx->impl.types_list->decl.type.type == T_ELMNTRY); tsoftassert(ctx->impl.types_list->decl.type.u.builtin == BT_Int); /* Check "someconst" */ tassert(ctx->impl.idents_list != NULL); tassert(ctx->impl.idents_root != NULL); tsoftassert(ctx->impl.idents_list->decl.type.type == T_FUNC); tn = tree_search(ctx, ctx->impl.idents_root, H_SOMEFUNC, 8, "somefunc"); tsoftassert(&ctx->impl.idents_list->decl.ident == tn); /* Check "somefunc" */ tassert(ctx->impl.idents_list->next != NULL); tsoftassert(ctx->impl.idents_list->next->decl.type.type == T_ARRAY); tsoftassert(ctx->impl.idents_list->next->next == NULL); tn = tree_search(ctx, ctx->impl.idents_root, H_SOMECONST, 9, "someconst"); tsoftassert(&ctx->impl.idents_list->next->decl.ident == tn); cleanup(); } #define ASSERT_TL_IDENT(root, hash, name, expected, ver) do { \ struct TopLevelIdent *tlident = (struct TopLevelIdent *)tree_search( \ ctx, (root), (hash), sizeof(name)-1, (name)); \ tassert(tlident != NULL); \ tsoftassert(tlident->decl.type.type == (expected)); \ if ((ver) == NULL) { \ tsoftassert(tlident->sinceversions == NULL); \ } else { \ tassert(tlident->sinceversions != NULL); \ tsoftassert(tlident->sinceversions->next == NULL); \ tsoftassert(tlident->sinceversions->version == (ver)); \ } \ } while (0) #define ASSERT_TL_IDENT_2VER(root, hash, name, expected, ver1, ver2) do { \ struct TopLevelIdent *tlident = (struct TopLevelIdent *)tree_search( \ ctx, (root), (hash), sizeof(name)-1, (name)); \ tassert(tlident != NULL); \ tsoftassert(tlident->decl.type.type == (expected)); \ tassert(tlident->sinceversions != NULL); \ tsoftassert(tlident->sinceversions->version == (ver1)); \ tassert(tlident->sinceversions->next != NULL); \ tsoftassert(tlident->sinceversions->next->version == (ver2)); \ tsoftassert(tlident->sinceversions->next->next == NULL); \ } while (0) static void test_idents_implemented(void) { struct ApiDef *ver100; single_module_test(__LINE__, CSLUL_MT_LIBRARY, /* Interface */ "since 1.0.0\n" "type SomeType = int\n" "since 1.0.0\n" "data [3]byte somedata\n" "since 1.0.0\n" "func somefunc(int x) -> byte\n", /* Implementation */ "type InternalType = int\n" "data [3]byte somedata = [1,2,3]\n" "func somefunc(int x) -> byte { return 123 }\n"); tassert(!ctx->has_fatal_errors); ver100 = (struct ApiDef *)lookup(ctx, ctx->module.apidefs_root, "1.0.0"); ASSERT_TL_IDENT(ctx->module.iface.types_root, H_SOMETYPE, "SomeType", T_ELMNTRY, ver100); ASSERT_TL_IDENT(ctx->module.iface.idents_root, H_SOMEDATA, "somedata", T_ARRAY, ver100); ASSERT_TL_IDENT(ctx->module.iface.idents_root, H_SOMEFUNC, "somefunc", T_FUNC, ver100); ASSERT_TL_IDENT(ctx->impl.types_root, H_INTERNALTYPE, "InternalType", T_ELMNTRY, NULL); ASSERT_TL_IDENT(ctx->impl.idents_root, H_SOMEDATA, "somedata", T_ARRAY, NULL); ASSERT_TL_IDENT(ctx->impl.idents_root, H_SOMEFUNC, "somefunc", T_FUNC, NULL); cleanup(); } static void test_idents_private(void) { struct ApiDef *ver100; single_module_test(__LINE__, CSLUL_MT_LIBRARY, /* Interface */ "since 1.0.0\n" "type PrivType\n", /* Implementation */ "type PrivType = struct { int x }\n"); tassert(!ctx->has_fatal_errors); ver100 = (struct ApiDef *)lookup(ctx, ctx->module.apidefs_root, "1.0.0"); ASSERT_TL_IDENT(ctx->module.iface.types_root, H_PRIVTYPE, "PrivType", T_PRIVATE, ver100); ASSERT_TL_IDENT(ctx->impl.types_root, H_PRIVTYPE, "PrivType", T_STRUCT, NULL); cleanup(); } static void test_idents_private_missing(void) { errors_in("main.slul"); expect_error(CSLUL_E_MISSINGIMPLDEF, mhlines+2, 6); single_module_test(__LINE__, CSLUL_MT_LIBRARY, /* Interface */ "since 1.0.0\n" "type PrivType\n", /* Implementation */ "type Other = struct { int x }\n"); cleanup(); } static void test_idents_private_badtype(void) { errors_in("source.slul"); expect_error(CSLUL_E_PRIVATENONSTRUCT, 1, 6); single_module_test(__LINE__, CSLUL_MT_LIBRARY, /* Interface */ "since 1.0.0\n" "type PrivType\n", /* Implementation */ "type PrivType = int\n"); cleanup(); } static void test_idents_impldupl_same(void) { errors_in("source.slul"); expect_error(CSLUL_E_IFACEIMPLDUPLICATE, 1, 6); single_module_test(__LINE__, CSLUL_MT_LIBRARY, /* Interface */ "since 1.0.0\n" "type Dup1 = int\n", /* Implementation */ "type Dup1 = int\n"); cleanup(); } static void test_idents_impldupl_different(void) { errors_in("source.slul"); expect_error(CSLUL_E_IFACEIMPLDUPLICATE, 1, 6); expect_error(CSLUL_E_IFACEIMPLDUPLICATE, 2, 10); single_module_test(__LINE__, CSLUL_MT_LIBRARY, /* Interface */ "since 1.0.0\n" "data [3]byte dup1\n" "since 1.0.0\n" "func dup2(int x) -> int\n", /* Implementation */ "func dup1(int x) -> [3]byte { return [1,2,3] }\n" "data int dup2 = 123\n"); cleanup(); } static void test_idents_implmissing(void) { /* The ordering of error messages is a bit weird: First, all types are checked in reverse (based on the iface). Then, all functions/data are checked in reverse. (And finally, expect_error also works in reverse order) */ errors_in("main.slul"); expect_error(CSLUL_E_MISSINGIMPLDEF, mhlines+2, 14); expect_error(CSLUL_E_MISSINGIMPLDEF, mhlines+4, 6); expect_error(CSLUL_E_MISSINGIMPLDEF, mhlines+6, 6); expect_error(CSLUL_E_MISSINGIMPLDEF, mhlines+8, 6); expect_error(CSLUL_E_MISSINGIMPLDEF, mhlines+10, 6); single_module_test(__LINE__, CSLUL_MT_LIBRARY, /* Interface */ "since 1.0.0\n" "data [3]byte somedata\n" "since 1.0.0\n" "func somefunc(int x) -> byte\n" "since 1.0.0\n" "type SomeType1\n" "since 1.0.1\n" "type SomeType2\n" "since 1.0.2\n" "type SomeType3\n", /* Implementation */ "func otherfunc(int x) -> byte { return 123 }\n"); cleanup(); } static void test_idents_versioned_good(void) { struct ApiDef *ver100, *ver101, *ver12, *ver102; single_module_test(__LINE__, CSLUL_MT_LIBRARY, /* Interface */ "since 1.0.0\n" "type SomeType\n" "since 1.0.0\n" "data [3]byte somedata\n" "since 1.0.1\n" "func somefunc(int x) -> byte\n" "since { 1.2 1.0.2 }\n" "func somefunc2(int x, int y) -> int\n", /* Implementation */ "type SomeType = struct { int x }\n" "data [3]byte somedata = [1,2,3]\n" "func somefunc(int x) -> byte { return 123 }\n" "func somefunc2(int x, int y) -> int { return 123 }\n"); tassert(!ctx->has_fatal_errors); ver100 = (struct ApiDef *)lookup(ctx, ctx->module.apidefs_root, "1.0.0"); ver101 = (struct ApiDef *)lookup(ctx, ctx->module.apidefs_root, "1.0.1"); ver12 = (struct ApiDef *)lookup(ctx, ctx->module.apidefs_root, "1.2"); ver102 = (struct ApiDef *)lookup(ctx, ctx->module.apidefs_root, "1.0.2"); /* Interface */ ASSERT_TL_IDENT(ctx->module.iface.types_root, H_SOMETYPE, "SomeType", T_PRIVATE, ver100); ASSERT_TL_IDENT(ctx->module.iface.idents_root, H_SOMEDATA, "somedata", T_ARRAY, ver100); ASSERT_TL_IDENT(ctx->module.iface.idents_root, H_SOMEFUNC, "somefunc", T_FUNC, ver101); ASSERT_TL_IDENT_2VER(ctx->module.iface.idents_root, H_SOMEFUNC2, "somefunc2", T_FUNC, ver12, ver102); /* Implementation */ ASSERT_TL_IDENT(ctx->impl.types_root, H_SOMETYPE, "SomeType", T_STRUCT, NULL); ASSERT_TL_IDENT(ctx->impl.idents_root, H_SOMEDATA, "somedata", T_ARRAY, NULL); ASSERT_TL_IDENT(ctx->impl.idents_root, H_SOMEFUNC, "somefunc", T_FUNC, NULL); ASSERT_TL_IDENT(ctx->impl.idents_root, H_SOMEFUNC2, "somefunc2", T_FUNC, NULL); cleanup(); } static void test_idents_versioned_bad(void) { /* Types in reverse, then funcs/data in reverse (and then expect_error calls have to be reversed) */ errors_in("main.slul"); expect_error(CSLUL_E_MISSINGIMPLDEF, mhlines+8, 14); /* somedata2 */ expect_error(CSLUL_E_MISSINGIMPLDEF, mhlines+12, 6); /* somefunc2 */ expect_error(CSLUL_E_MISSINGIMPLDEF, mhlines+4, 6); /* SomeType2 */ errors_in("source.slul"); expect_error(CSLUL_E_SINCEVERSIONINIMPL, 8, 1); /* somefunc1 */ expect_error(CSLUL_E_SINCEVERSIONINIMPL, 4, 1); /* somedata1 */ expect_error(CSLUL_E_SINCEVERSIONINIMPL, 1, 1); /* SomeType1 */ single_module_test(__LINE__, CSLUL_MT_LIBRARY, /* Interface */ "since 1.0.0\n" "type SomeType1\n" "since 1.0.0\n" "type SomeType2\n" "since 1.0.1\n" "data [3]byte somedata1\n" "since 1.0.0\n" "data [3]byte somedata2\n" "since 1.0.0\n" "func somefunc1(int x) -> byte\n" "since 1.0.1\n" "func somefunc2(int x, int y) -> int\n", /* Implementation */ "since 1.0.0\n" "type SomeType1 = struct { int x }\n" /* has version in impl */ "type OtherType = struct { int x }\n" /* other name */ "since { 1.0.0 1.0.1 }\n" "data [3]byte somedata1 = [1,2,3]\n" /* has versions in impl */ "data [3]byte otherdata = [1,2,3]\n" /* other name */ "func somefunc1(int x) -> byte { return 123 }\n" "since 1.0.2\n" "func otherfunc(int x, int y) -> int { return 123 }\n"); cleanup(); } static void test_idents_structversions_bad(void) { errors_in("main.slul"); expect_error(CSLUL_E_UNVERSIONEDAFTERVERSIONED, mhlines+20, 5); /* f10 */ expect_error(CSLUL_E_SINCEVERNOTCHRONOLOGICAL, mhlines+18, 5); /* f9 */ expect_error(CSLUL_E_NOTABACKPORT, mhlines+16, 19); /* f8 */ expect_error(CSLUL_E_SAMEVERSION, mhlines+14, 19); /* f7 */ expect_error(CSLUL_E_VERSIONEARLIERTHANDECL, mhlines+12, 19); /* f6 */ expect_error(CSLUL_E_SAMEVERSION, mhlines+8, 13); /* f4 */ expect_error(CSLUL_E_SAMEVERSION, mhlines+6, 11); /* f3 */ expect_error(CSLUL_E_VERSIONEARLIERTHANDECL, mhlines+4, 11); /* f2 */ single_module_test(__LINE__, CSLUL_MT_LIBRARY, /* Interface */ "since 1.0.1\n" "type Struct1 = struct {\n" " int f1_ok\n" " since 1.0.0\n" " int f2\n" " since 1.0.1\n" " int f3\n" " since { 1.0.1 }\n" " int f4\n" " since 1.2\n" " int f5_ok\n" " since { 1.0.2 1.0.0 }\n" " int f6\n" " since { 1.0.2 1.0.1 }\n" " int f7\n" " since { 1.0.2 1.2 }\n" " int f8\n" " since 1.2\n" " int f9\n" " int f10\n" "}\n", /* Implementation */ "\n"); cleanup(); } static void test_idents_enumversions_bad(void) { errors_in("main.slul"); expect_error(CSLUL_E_UNVERSIONEDAFTERVERSIONED, mhlines+20, 5); /* ev10 */ expect_error(CSLUL_E_SINCEVERNOTCHRONOLOGICAL, mhlines+18, 5); /* ev9 */ expect_error(CSLUL_E_NOTABACKPORT, mhlines+16, 19); /* ev8 */ expect_error(CSLUL_E_SAMEVERSION, mhlines+14, 19); /* ev7 */ expect_error(CSLUL_E_VERSIONEARLIERTHANDECL, mhlines+12, 19); /* ev6 */ expect_error(CSLUL_E_SAMEVERSION, mhlines+8, 13); /* ev4 */ expect_error(CSLUL_E_SAMEVERSION, mhlines+6, 11); /* ev3 */ expect_error(CSLUL_E_VERSIONEARLIERTHANDECL, mhlines+4, 11); /* ev2 */ single_module_test(__LINE__, CSLUL_MT_LIBRARY, /* Interface */ "since 1.0.1\n" "type Enum1 = enum {\n" " enumv1_ok\n" " since 1.0.0\n" " enumv2\n" " since 1.0.1\n" " enumv3\n" " since { 1.0.1 }\n" " enumv4\n" " since 1.2\n" " enumv5_ok\n" " since { 1.0.2 1.0.0 }\n" " enumv6\n" " since { 1.0.2 1.0.1 }\n" " enumv7\n" " since { 1.0.2 1.2 }\n" " enumv8\n" " since 1.2\n" " enumv9\n" " enumv10\n" "}\n", /* Implementation */ "\n"); cleanup(); } static void test_idents_nested_since(void) { errors_in("main.slul"); expect_error(CSLUL_E_NESTEDSINCE, mhlines+6, 9); single_module_test(__LINE__, CSLUL_MT_LIBRARY, /* Interface */ "since 1.0.1\n" "type Struct1 = struct {\n" " since 1.0.2\n" " [2]struct {\n" " int f1\n" " since 1.0.2\n" " int f2\n" " } arr\n" "}\n", /* Implementation */ "\n"); cleanup(); } static void test_implversion_good1(void) { static const char main_slul[] = "\\slul 0.0.0 impl 0.0.0\n" "\\name lib\n" "\\type library\n" "\\version 1.0.1 unstable_api\n" "\\source source.slul\n" "\n" "func f()\n"; static const char source_slul[] = "func f() {\n" "}\n"; make_ctx(); sourceline = __LINE__; set_mocked_file(&mocked_mainfile, main_slul, strlen(main_slul)); set_mocked_file(&mocked_srcfile, source_slul, strlen(source_slul)); cslul_build(ctx, NULL); if (!ctx->has_fatal_errors) { /* skip when testing out-of-memory */ tsoftassert(ctx->module.effective_minlangver == LANGVER_0_0_0); tsoftassert(ctx->module.effective_maxlangver == LANGVER_0_0_0); } cleanup(); } static void test_implversion_good2(void) { /* TODO update "upto" once there are at two different versions */ static const char main_slul[] = "\\slul 0.0.0 impl 0.0.0 upto 0.0.0\n" "\\name lib\n" "\\type library\n" "\\version 1.0.1 unstable_api\n" "\\source source.slul\n" "\n" "func f()\n"; static const char source_slul[] = "func f() {\n" "}\n"; make_ctx(); sourceline = __LINE__; set_mocked_file(&mocked_mainfile, main_slul, strlen(main_slul)); set_mocked_file(&mocked_srcfile, source_slul, strlen(source_slul)); cslul_build(ctx, NULL); if (!ctx->has_fatal_errors) { /* skip when testing out-of-memory */ tsoftassert(ctx->module.effective_minlangver == LANGVER_0_0_0); tsoftassert(ctx->module.effective_maxlangver == LANGVER_0_0_0); } cleanup(); } static void test_implversion_bad(void) { static const char main_slul[] = "\\slul 0.0.0 impl 999.999.999\n" "\\name lib\n" "\\type library\n" "\\version 1.0.1 unstable_api\n" "\\source source.slul\n" "\n" "func f()\n"; static const char source_slul[] = "func f() {\n" "}\n"; make_ctx(); sourceline = __LINE__; set_mocked_file(&mocked_mainfile, main_slul, strlen(main_slul)); set_mocked_file(&mocked_srcfile, source_slul, strlen(source_slul)); errors_in("main.slul"); expect_error(CSLUL_E_UNSUPPORTEDIMPLLANGVER, 0, 0); cslul_build(ctx, NULL); cleanup(); } static void multi_module_test(int srcline, const char *mainslul, const char *impl, const char *someiface, const char *otheriface) { sourceline = srcline; set_mocked_file(&mocked_mainfile, mainslul, strlen(mainslul)); set_mocked_file(&mocked_srcfile, impl, strlen(impl)); set_mocked_file(&mocked_someiface, someiface, strlen(someiface)); set_mocked_file(&mocked_otheriface, otheriface, strlen(otheriface)); make_config(); ctx = create_ctx_cfg(CSLUL_P_INIT, cfg); tassert(ctx != NULL); cslul_build(ctx, NULL); } static const char multidep_app_mainslul[] = "\\slul 0.0.0\n" "\\name mainapp\n" "\\version 1.0.1\n" "\\depends somelib 1.2.0\n" "\\depends otherlib 1.1.1\n" "\\source source.slul\n" "\n" "type MainType2 = struct {\n" " ref SomeType2 st\n" "}\n" "func f(ref SomeType1 st) {\n" " ref SomeType2 st\n" "}\n"; static const char multidep_lib_mainslul[] = "\\slul 0.0.0\n" "\\name mainlib\n" "\\type library\n" "\\version 1.0.1 unstable_api\n" "\\interface_depends somelib 1.2.0\n" "\\interface_depends otherlib 1.1.1\n" "\\source source.slul\n" "\n" "type MainType\n" "type MainType2 = struct {\n" " ref SomeType2 st\n" "}\n"; static const char multidep_sourceslul[] = "type MainType = struct {\n" " ref SomeType1 st1\n" " ref SomeType2 st2\n" "}\n"; #define LOOKUP_TD(root, name) ((struct TypeDecl *)lookup(ctx, (root), (name))) static struct Module *lookup_module(const char *name) { /* modules don't have any hashcode yet */ size_t len = strlen(name); struct CSlulDependency *dep = (struct CSlulDependency *) tree_search(ctx, ctx->module.deps_root, hash_str(name, len), len, name); return dep ? dep->module : NULL; } /** Checks that a type is a reference is an imported symbol from another module, and that it has been bound to "expected_decl". */ static void assert_imported_typeref(const struct Type *type, const struct TypeDecl *expected_decl) { tassert(type != NULL); tassert(type->type == T_IDENT); tassert(type->u.ident != NULL); tassert(type->u.ident->type.type == T_IMPORTED); tassert(type->u.ident->type.u.ident == expected_decl); } /** Used by test_crossmodule_types_app/lib */ static void check_crossmod_types(const struct TypeDecl *tdmain2, const struct TypeDecl *tdimpl) { const struct TypeDecl *tdsome1, *tdsome2, *tdother; const struct FieldOrParamEntry *field1, *field2; const struct Module *somelib, *otherlib; tassert(!ctx->has_fatal_errors); somelib = lookup_module("somelib"); tassert(somelib != NULL); otherlib = lookup_module("otherlib"); tassert(otherlib != NULL); tdsome1 = LOOKUP_TD(somelib->iface.types_root, "SomeType1"); tassert(tdsome1 != NULL); tassert(tdsome1->type.type == T_PRIVATE); tdsome2 = LOOKUP_TD(somelib->iface.types_root, "SomeType2"); tassert(tdsome2 != NULL); tassert(tdsome2->type.type == T_STRUCT); tdother = LOOKUP_TD(otherlib->iface.types_root, "OtherType"); tassert(tdother != NULL); tassert(tdother->type.type == T_PRIVATE); /* Check refs from source.slul */ tassert(tdimpl->type.u.fields != NULL); tassert(tdimpl->type.u.fields->count == 2); tassert((field1 = tdimpl->type.u.fields->first) != NULL); tassert(field1->f.vardef.decl.type.type == T_REF); assert_imported_typeref(field1->f.vardef.decl.type.u.nested, tdsome1); tsoftassert(field1->f.vardef.decl.u.initval == NULL); tassert((field2 = field1->next) != NULL); tassert(field2->f.vardef.decl.type.type == T_REF); assert_imported_typeref(field2->f.vardef.decl.type.u.nested, tdsome2); tsoftassert(field2->f.vardef.decl.u.initval == NULL); tsoftassert(field2->next == NULL); /* Check ref from main.slul */ tassert(tdmain2->type.u.fields != NULL); tassert(tdmain2->type.u.fields->count == 1); tassert((field1 = tdmain2->type.u.fields->first) != NULL); tassert(field1->f.vardef.decl.type.type == T_REF); assert_imported_typeref(field1->f.vardef.decl.type.u.nested, tdsome2); tsoftassert(field1->f.vardef.decl.u.initval == NULL); tsoftassert(field1->next == NULL); /* Check ref from somlib.slul */ tassert(tdsome2->type.u.fields != NULL); tassert(tdsome2->type.u.fields->count == 1); tassert((field1 = tdsome2->type.u.fields->first) != NULL); tassert(field1->f.vardef.decl.type.type == T_REF); assert_imported_typeref(field1->f.vardef.decl.type.u.nested, tdother); tsoftassert(field1->f.vardef.decl.u.initval == NULL); tsoftassert(field1->next == NULL); } /** Test references from an application module (i.e. from implementation to interface via \depends) */ static void test_crossmodule_types_app(void) { const struct TypeDecl *tdmain2, *tdimpl; multi_module_test(__LINE__, /* main.slul */ multidep_app_mainslul, /* source.slul */ multidep_sourceslul, /* somelib */ "\\slul 0.0.0\n" "\\name somelib\n" "\\type library\n" "\\version 1.2.0\n" "\\interface_depends otherlib 1.1.1 since 1.0.0\n" "\\api_def 1.0.0 10010010010010010010010010010010\n" "\\api_def 1.2.0 12012012012012012012012012012012\n" "\\source someimpl.slul\n" "\n" "since 1.0.0\n" "type SomeType1\n" "since 1.2.0\n" "type SomeType2 = struct {\n" " ref OtherType ot\n" "}\n", /* otherlib */ "\\slul 0.0.0\n" "\\name otherlib\n" "\\type library\n" "\\version 1.1.1 unstable_api\n" "\\source otherimpl.slul\n" "\n" "type OtherType\n" ); tassert(!ctx->has_errors); tdmain2 = LOOKUP_TD(ctx->impl.types_root, "MainType2"); tassert(tdmain2 != NULL); tassert(tdmain2->type.type == T_STRUCT); tdimpl = LOOKUP_TD(ctx->impl.types_root, "MainType"); tassert(tdimpl != NULL); tassert(tdimpl->type.type == T_STRUCT); check_crossmod_types(tdmain2, tdimpl); cleanup(); } /** Test references from a library module via \interface_depends */ static void test_crossmodule_types_lib(void) { const struct TypeDecl *tdmain, *tdmain2, *tdimpl; multi_module_test(__LINE__, /* main.slul */ multidep_lib_mainslul, /* source.slul */ multidep_sourceslul, /* somelib */ "\\slul 0.0.0\n" "\\name somelib\n" "\\type library\n" "\\version 1.2.0 unstable_api\n" "\\interface_depends otherlib 1.1.1\n" "\\source someimpl.slul\n" "\n" "type SomeType1\n" "type SomeType2 = struct {\n" " ref OtherType ot\n" "}\n", /* otherlib */ "\\slul 0.0.0\n" "\\name otherlib\n" "\\type library\n" "\\version 1.1.1 unstable_api\n" "\\source otherimpl.slul\n" "\n" "type OtherType\n" ); tassert(!ctx->has_errors); tdmain = LOOKUP_TD(ctx->module.iface.types_root, "MainType"); tassert(tdmain != NULL); tassert(tdmain->type.type == T_PRIVATE); tdimpl = LOOKUP_TD(ctx->impl.types_root, "MainType"); tassert(tdimpl != NULL); tassert(tdimpl->type.type == T_STRUCT); tdmain2 = LOOKUP_TD(ctx->module.iface.types_root, "MainType2"); tassert(tdmain2 != NULL); tassert(tdmain2->type.type == T_STRUCT); check_crossmod_types(tdmain2, tdimpl); cleanup(); } /** Test of references to undefined types */ static void test_crossmodule_types_notdef(void) { errors_in("source.slul"); expect_error(CSLUL_E_TLTYPENOTDEF, 3, 9); errors_in("main.slul"); expect_error(CSLUL_E_TLTYPENOTDEF, 11, 9); errors_in("#interface dir#somelib.slul"); expect_error(CSLUL_E_TLTYPENOTDEF, 8, 19); multi_module_test(__LINE__, /* main.slul */ multidep_lib_mainslul, /* source.slul */ multidep_sourceslul, /* somelib */ "\\slul 0.0.0\n" "\\name somelib\n" "\\type library\n" "\\version 1.2.0 unstable_api\n" "\\source someimpl.slul\n" "\n" "type SomeType1\n" "func somefunc(ref SomeType3 x) -> uint\n", /* otherlib */ "\\slul 0.0.0\n" "\\name otherlib\n" "\\type library\n" "\\version 1.1.1 unstable_api\n" "\\source otherimpl.slul\n" "\n" "type OtherType\n" ); cleanup(); } /** Test of ambiguous reference in app impl */ static void test_crossmodule_types_ambiguous_app(void) { errors_in("main.slul"); expect_error(CSLUL_E_AMBIGUOUSIDENT, 11, 12); multi_module_test(__LINE__, /* main.slul */ multidep_app_mainslul, /* source.slul */ multidep_sourceslul, /* somelib */ "\\slul 0.0.0\n" "\\name somelib\n" "\\type library\n" "\\version 1.2.0 unstable_api\n" "\\source someimpl.slul\n" "\n" "type SomeType1\n" "type SomeType2\n", /* otherlib */ "\\slul 0.0.0\n" "\\name otherlib\n" "\\type library\n" "\\version 1.1.1 unstable_api\n" "\\source otherimpl.slul\n" "\n" "type SomeType1\n" ); cleanup(); } /** Test of ambiguous reference in lib impl */ static void test_crossmodule_types_ambiguous_lib(void) { errors_in("source.slul"); expect_error(CSLUL_E_AMBIGUOUSIDENT, 2, 9); multi_module_test(__LINE__, /* main.slul */ multidep_lib_mainslul, /* source.slul */ multidep_sourceslul, /* somelib */ "\\slul 0.0.0\n" "\\name somelib\n" "\\type library\n" "\\version 1.2.0\n" "\\api_def 1.0.0\n" "\\api_def 1.2.0\n" "\\source someimpl.slul\n" "\n" "since 1.2.0\n" "type SomeType1\n" "since 1.2.0\n" "type SomeType2\n", /* otherlib */ "\\slul 0.0.0\n" "\\name otherlib\n" "\\type library\n" "\\version 1.1.1\n" "\\api_def 1.1.1\n" "\\source otherimpl.slul\n" "\n" "since 1.1.1\n" "type SomeType1\n" ); cleanup(); } /** Test of unused ambiguous reference in impl. This is not an error. */ static void test_crossmodule_types_ambiguous_unused(void) { multi_module_test(__LINE__, /* main.slul */ multidep_lib_mainslul, /* source.slul */ multidep_sourceslul, /* somelib */ "\\slul 0.0.0\n" "\\name somelib\n" "\\type library\n" "\\version 1.2.0 unstable_api\n" "\\source someimpl.slul\n" "\n" "type SomeType1\n" "type SomeType2\n" "type Ambiguous\n", /* not used */ /* otherlib */ "\\slul 0.0.0\n" "\\name otherlib\n" "\\type library\n" "\\version 1.1.1 unstable_api\n" "\\source otherimpl.slul\n" "\n" "type Ambiguous\n" /* not used */ ); cleanup(); } /** Test of an identifier that is referenced both from the impl and from an iface */ static void test_crossmodule_types_doubleref(void) { multi_module_test(__LINE__, /* main.slul */ multidep_app_mainslul, /* source.slul */ multidep_sourceslul, /* somelib */ "\\slul 0.0.0\n" "\\name somelib\n" "\\type library\n" "\\version 1.2.0 unstable_api\n" "\\interface_depends otherlib 1.1.1\n" "\\source someimpl.slul\n" "\n" "type SomeType1 = struct {\n" " ref SomeType2 xx\n" "}\n", /* otherlib */ "\\slul 0.0.0\n" "\\name otherlib\n" "\\type library\n" "\\version 1.1.1 unstable_api\n" "\\interface_depends somelib 1.2.0\n" "\\source otherimpl.slul\n" "\n" "type SomeType2 = struct {\n" " ref SomeType1 yy\n" "}\n" ); cleanup(); } const TestFunctionInfo tests_tlverify[] = { TEST_BEFORE(before_test) TEST_AFTER(after_test) TEST_INFO(test_idents_internal) TEST_INFO(test_idents_implemented) TEST_INFO(test_idents_private) TEST_INFO(test_idents_private_missing) TEST_INFO(test_idents_private_badtype) TEST_INFO(test_idents_impldupl_same) TEST_INFO(test_idents_impldupl_different) TEST_INFO(test_idents_implmissing) TEST_INFO(test_idents_versioned_good) TEST_INFO(test_idents_versioned_bad) TEST_INFO(test_idents_structversions_bad) TEST_INFO(test_idents_enumversions_bad) TEST_INFO(test_idents_nested_since) TEST_INFO(test_implversion_good1) TEST_INFO(test_implversion_good2) TEST_INFO(test_implversion_bad) TEST_INFO(test_crossmodule_types_app) TEST_INFO(test_crossmodule_types_lib) TEST_INFO(test_crossmodule_types_notdef) TEST_INFO(test_crossmodule_types_ambiguous_app) TEST_INFO(test_crossmodule_types_ambiguous_lib) TEST_INFO(test_crossmodule_types_ambiguous_unused) TEST_INFO(test_crossmodule_types_doubleref) TEST_END };