| /* Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include <string.h> |
| |
| #define CFC_USE_TEST_MACROS |
| #include "CFCBase.h" |
| #include "CFCClass.h" |
| #include "CFCFileSpec.h" |
| #include "CFCFunction.h" |
| #include "CFCMethod.h" |
| #include "CFCParamList.h" |
| #include "CFCParcel.h" |
| #include "CFCParser.h" |
| #include "CFCSymbol.h" |
| #include "CFCTest.h" |
| #include "CFCType.h" |
| #include "CFCUtil.h" |
| #include "CFCVariable.h" |
| |
| #ifndef true |
| #define true 1 |
| #define false 0 |
| #endif |
| |
| static void |
| S_run_tests(CFCTest *test); |
| |
| static int |
| S_has_symbol(CFCSymbol **symbols, const char *name); |
| |
| const CFCTestBatch CFCTEST_BATCH_CLASS = { |
| "Clownfish::CFC::Model::Class", |
| 93, |
| S_run_tests |
| }; |
| |
| static char* |
| S_try_create(CFCParcel *parcel, const char *name, const char *nickname) { |
| char *error; |
| |
| CFCUTIL_TRY { |
| CFCClass *klass = CFCClass_create(parcel, NULL, name, nickname, NULL, |
| NULL, NULL, false, false, false); |
| CFCBase_decref((CFCBase*)klass); |
| } |
| CFCUTIL_CATCH(error); |
| |
| return error; |
| } |
| |
| static void |
| S_run_tests(CFCTest *test) { |
| CFCParser *parser = CFCParser_new(); |
| |
| CFCParcel *neato = CFCTest_parse_parcel(test, parser, "parcel Neato;"); |
| CFCFileSpec *file_spec = CFCFileSpec_new(".", "Foo/FooJr", ".cfh", 0); |
| CFCClass *thing_class |
| = CFCTest_parse_class(test, parser, "class Thing {}"); |
| CFCClass *widget_class |
| = CFCTest_parse_class(test, parser, "class Widget {}"); |
| |
| CFCVariable *thing; |
| CFCVariable *widget; |
| CFCFunction *tread_water; |
| |
| { |
| CFCType *thing_type = CFCTest_parse_type(test, parser, "Thing*"); |
| thing = CFCVariable_new(NULL, "thing", thing_type, 0); |
| |
| CFCType *widget_type = CFCTest_parse_type(test, parser, "Widget*"); |
| widget = CFCVariable_new(NULL, "widget", widget_type, 0); |
| |
| CFCType *return_type = CFCTest_parse_type(test, parser, "void"); |
| CFCParamList *param_list |
| = CFCTest_parse_param_list(test, parser, "()"); |
| tread_water = CFCFunction_new(NULL, "tread_water", return_type, |
| param_list, NULL, 0); |
| |
| CFCBase_decref((CFCBase*)thing_type); |
| CFCBase_decref((CFCBase*)widget_type); |
| CFCBase_decref((CFCBase*)return_type); |
| CFCBase_decref((CFCBase*)param_list); |
| } |
| |
| CFCClass *foo |
| = CFCClass_create(neato, NULL, "Foo", NULL, NULL, NULL, NULL, false, |
| false, false); |
| CFCClass_add_function(foo, tread_water); |
| CFCClass_add_member_var(foo, thing); |
| CFCClass_add_inert_var(foo, widget); |
| |
| { |
| CFCClass *should_be_foo = CFCParcel_class(neato, "Foo"); |
| OK(test, should_be_foo == foo, "Fetch class from parcel"); |
| } |
| |
| { |
| char *error = S_try_create(neato, "Foo", NULL); |
| OK(test, error && strstr(error, "Two classes with name"), |
| "Can't call create for the same class more than once"); |
| FREEMEM(error); |
| } |
| |
| { |
| char *error = S_try_create(neato, "Other::Foo", NULL); |
| OK(test, error && strstr(error, "Class name conflict"), |
| "Can't create classes wth the same final component"); |
| FREEMEM(error); |
| } |
| |
| { |
| char *error = S_try_create(neato, "Bar", "Foo"); |
| OK(test, error && strstr(error, "Class nickname conflict"), |
| "Can't create classes wth the same nickname"); |
| FREEMEM(error); |
| } |
| |
| CFCClass *foo_jr |
| = CFCClass_create(neato, NULL, "Foo::FooJr", NULL, NULL, NULL, "Foo", |
| false, false, false); |
| STR_EQ(test, CFCClass_get_struct_sym(foo_jr), "FooJr", |
| "get_struct_sym"); |
| STR_EQ(test, CFCClass_full_struct_sym(foo_jr), "neato_FooJr", |
| "full_struct_sym"); |
| STR_EQ(test, CFCClass_get_nickname(foo_jr), "FooJr", |
| "derive class nickname from class name"); |
| |
| CFCClass *final_foo |
| = CFCClass_create(neato, NULL, "Foo::FooJr::FinalFoo", NULL, NULL, |
| file_spec, "Foo::FooJr", true, false, false); |
| OK(test, CFCClass_final(final_foo), "final"); |
| STR_EQ(test, CFCClass_include_h(final_foo), "Foo/FooJr.h", |
| "include_h uses path_part"); |
| STR_EQ(test, CFCClass_get_parent_class_name(final_foo), "Foo::FooJr", |
| "get_parent_class_name"); |
| |
| { |
| CFCParcel *parsed_neato |
| = CFCTest_parse_parcel(test, parser, "parcel Neato;"); |
| CFCBase_decref((CFCBase*)parsed_neato); |
| } |
| |
| CFCParser_set_parcel(parser, neato); |
| CFCParser_set_class(parser, foo); |
| CFCMethod *do_stuff |
| = CFCTest_parse_method(test, parser, "void Do_Stuff(Foo *self);"); |
| CFCClass_add_method(foo, do_stuff); |
| |
| CFCClass *inert_foo |
| = CFCClass_create(neato, NULL, "InertFoo", NULL, NULL, NULL, NULL, |
| false, true, false); |
| |
| { |
| CFCParser_set_class(parser, inert_foo); |
| CFCMethod *inert_do_stuff |
| = CFCTest_parse_method(test, parser, |
| "void Do_Stuff(InertFoo *self);"); |
| char *error; |
| |
| CFCUTIL_TRY { |
| CFCClass_add_method(inert_foo, inert_do_stuff); |
| } |
| CFCUTIL_CATCH(error); |
| OK(test, error && strstr(error, "inert class"), |
| "Error out on conflict between inert attribute and object method"); |
| |
| FREEMEM(error); |
| CFCBase_decref((CFCBase*)inert_do_stuff); |
| } |
| |
| { |
| char *error; |
| CFCUTIL_TRY { |
| CFCClass_add_child(foo, inert_foo); |
| } |
| CFCUTIL_CATCH(error); |
| OK(test, error && strstr(error, "Inert class"), |
| "inert class can't inherit"); |
| FREEMEM(error); |
| } |
| |
| { |
| char *error; |
| CFCUTIL_TRY { |
| CFCClass_add_child(inert_foo, foo); |
| } |
| CFCUTIL_CATCH(error); |
| OK(test, error && strstr(error, "from inert class"), |
| "can't inherit from inert class"); |
| FREEMEM(error); |
| } |
| |
| CFCClass_resolve_types(foo); |
| CFCClass_resolve_types(foo_jr); |
| CFCClass_resolve_types(final_foo); |
| |
| CFCClass_add_child(foo, foo_jr); |
| CFCClass_add_child(foo_jr, final_foo); |
| |
| { |
| CFCClass *bar |
| = CFCClass_create(neato, NULL, "Foo::FooJr::FinalFoo::Bar", NULL, |
| NULL, NULL, "Foo::FooJr::FinalFoo", false, false, |
| false); |
| char *error; |
| |
| CFCUTIL_TRY { |
| CFCClass_add_child(final_foo, bar); |
| } |
| CFCUTIL_CATCH(error); |
| OK(test, error && strstr(error, "final class"), |
| "Can't add_child to final class"); |
| |
| FREEMEM(error); |
| CFCBase_decref((CFCBase*)bar); |
| } |
| |
| CFCClass_grow_tree(foo); |
| |
| { |
| char *error; |
| CFCUTIL_TRY { |
| CFCClass_grow_tree(foo); |
| } |
| CFCUTIL_CATCH(error); |
| OK(test, error && strstr(error, "grow_tree"), |
| "call grow_tree only once."); |
| FREEMEM(error); |
| } |
| |
| { |
| char *error; |
| CFCUTIL_TRY { |
| CFCClass_add_method(foo_jr, do_stuff); |
| } |
| CFCUTIL_CATCH(error); |
| OK(test, error && strstr(error, "grow_tree"), |
| "Forbid add_method after grow_tree."); |
| FREEMEM(error); |
| } |
| |
| OK(test, CFCClass_get_parent(foo_jr) == foo, "grow_tree, one level" ); |
| OK(test, CFCClass_get_parent(final_foo) == foo_jr, |
| "grow_tree, two levels"); |
| OK(test, CFCClass_fresh_method(foo, "Do_Stuff") == do_stuff, |
| "fresh_method"); |
| OK(test, CFCClass_method(foo_jr, "Do_Stuff") == do_stuff, |
| "inherited method"); |
| OK(test, CFCClass_fresh_method(foo_jr, "Do_Stuff") == NULL, |
| "inherited method not 'fresh'"); |
| OK(test, CFCMethod_final(CFCClass_method(final_foo, "Do_Stuff")), |
| "Finalize inherited method"); |
| OK(test, !CFCMethod_final(CFCClass_method(foo_jr, "Do_Stuff")), |
| "Don't finalize method in parent"); |
| |
| { |
| CFCVariable **inert_vars = CFCClass_inert_vars(foo); |
| OK(test, inert_vars[0] == widget, "inert_vars[0]"); |
| OK(test, inert_vars[1] == NULL, "inert_vars[1]"); |
| |
| CFCFunction **functions = CFCClass_functions(foo); |
| OK(test, functions[0] == tread_water, "functions[0]"); |
| OK(test, functions[1] == NULL, "functions[1]"); |
| |
| CFCMethod **methods = CFCClass_methods(foo); |
| OK(test, methods[0] == do_stuff, "methods[0]"); |
| OK(test, methods[1] == NULL, "methods[1]"); |
| |
| CFCMethod **fresh_methods = CFCClass_fresh_methods(foo); |
| OK(test, fresh_methods[0] == do_stuff, "fresh_methods[0]"); |
| OK(test, fresh_methods[1] == NULL, "fresh_methods[1]"); |
| |
| CFCVariable **fresh_member_vars = CFCClass_fresh_member_vars(foo); |
| OK(test, fresh_member_vars[0] == thing, "fresh_member_vars[0]"); |
| OK(test, fresh_member_vars[1] == NULL, "fresh_member_vars[1]"); |
| } |
| |
| { |
| CFCVariable **member_vars = CFCClass_member_vars(foo_jr); |
| OK(test, member_vars[0] == thing, "member_vars[0]"); |
| OK(test, member_vars[1] == NULL, "member_vars[1]"); |
| |
| CFCFunction **functions = CFCClass_functions(foo_jr); |
| OK(test, functions[0] == NULL, "functions[0]"); |
| |
| CFCVariable **fresh_member_vars = CFCClass_fresh_member_vars(foo_jr); |
| OK(test, fresh_member_vars[0] == NULL, "fresh_member_vars[0]"); |
| |
| CFCVariable **inert_vars = CFCClass_inert_vars(foo_jr); |
| OK(test, inert_vars[0] == NULL, "inert_vars[0]"); |
| } |
| |
| { |
| CFCMethod **fresh_methods = CFCClass_fresh_methods(final_foo); |
| OK(test, fresh_methods[0] == NULL, "fresh_methods[0]"); |
| } |
| |
| { |
| CFCClass *final_class |
| = CFCTest_parse_class(test, parser, "final class Iamfinal { }"); |
| OK(test, CFCClass_final(final_class), "class modifer: final"); |
| CFCClass *inert_class |
| = CFCTest_parse_class(test, parser, "inert class Iaminert { }"); |
| OK(test, CFCClass_inert(inert_class), "class modifer: inert"); |
| |
| CFCBase_decref((CFCBase*)final_class); |
| CFCBase_decref((CFCBase*)inert_class); |
| } |
| |
| { |
| static const char *names[2] = { "Fooble", "Foo::FooJr::FooIII" }; |
| for (int i = 0; i < 2; ++i) { |
| const char *name = names[i]; |
| char *class_src |
| = CFCUtil_sprintf("class Fu::%s inherits %s { }", name, name); |
| CFCClass *klass = CFCTest_parse_class(test, parser, class_src); |
| STR_EQ(test, CFCClass_get_parent_class_name(klass), name, |
| "class_inheritance: %s", name); |
| FREEMEM(class_src); |
| CFCBase_decref((CFCBase*)klass); |
| } |
| } |
| |
| { |
| const char *class_src = |
| "public class Foo::Foodie nickname Foodie inherits Foo {\n" |
| " int num;\n" |
| "}\n"; |
| CFCClass *klass = CFCTest_parse_class(test, parser, class_src); |
| CFCSymbol **member_vars |
| = (CFCSymbol**)CFCClass_fresh_member_vars(klass); |
| OK(test, S_has_symbol(member_vars, "num"), |
| "parsed member var"); |
| |
| CFCBase_decref((CFCBase*)klass); |
| } |
| |
| { |
| const char *class_src = |
| "/**\n" |
| " * Bow wow.\n" |
| " *\n" |
| " * Wow wow wow.\n" |
| " */\n" |
| "public class Animal::Dog inherits Animal {\n" |
| " public inert Dog* init(Dog *self, String *name,\n" |
| " String *fave_food);\n" |
| " inert uint32_t count();\n" |
| " inert uint64_t num_dogs;\n" |
| " public inert Dog *top_dog;\n" |
| "\n" |
| " String *name;\n" |
| " bool likes_to_go_fetch;\n" |
| " ChewToy *squishy;\n" |
| " Owner *mom;\n" |
| "\n" |
| " void Destroy(Dog *self);\n" |
| " public String* Bark(Dog *self);\n" |
| " public void Eat(Dog *self);\n" |
| " public void Bite(Dog *self, Enemy *enemy);\n" |
| " public Thing *Fetch(Dog *self, Thing *thing);\n" |
| " public final void Bury(Dog *self, Bone *bone);\n" |
| " public abstract incremented nullable Thing*\n" |
| " Scratch(Dog *self);\n" |
| "\n" |
| " int32_t[1] flexible_array_at_end_of_struct;\n" |
| "}\n"; |
| CFCClass *klass = CFCTest_parse_class(test, parser, class_src); |
| |
| CFCSymbol **inert_vars = (CFCSymbol**)CFCClass_inert_vars(klass); |
| CFCSymbol **member_vars |
| = (CFCSymbol**)CFCClass_fresh_member_vars(klass); |
| CFCSymbol **functions = (CFCSymbol**)CFCClass_functions(klass); |
| CFCSymbol **methods = (CFCSymbol**)CFCClass_fresh_methods(klass); |
| OK(test, S_has_symbol(inert_vars, "num_dogs"), "parsed inert var"); |
| OK(test, S_has_symbol(inert_vars, "top_dog"), "parsed public inert var"); |
| OK(test, S_has_symbol(member_vars, "mom"), "parsed member var"); |
| OK(test, S_has_symbol(member_vars, "squishy"), "parsed member var"); |
| OK(test, S_has_symbol(functions, "init"), "parsed function"); |
| OK(test, S_has_symbol(methods, "Destroy"), "parsed parcel method"); |
| OK(test, S_has_symbol(methods, "Bury"), "parsed public method"); |
| OK(test, S_has_symbol(methods, "Scratch"), |
| "parsed public abstract nullable method"); |
| |
| CFCMethod *scratch = CFCClass_fresh_method(klass, "Scratch"); |
| OK(test, scratch != NULL, "find method 'Scratch'"); |
| OK(test, CFCType_nullable(CFCMethod_get_return_type(scratch)), |
| "public abstract incremented nullable flagged as nullable"); |
| |
| int num_public_methods = 0; |
| for (int i = 0; methods[i]; ++i) { |
| if (CFCSymbol_public(methods[i])) { ++num_public_methods; } |
| } |
| INT_EQ(test, num_public_methods, 6, "pass acl to Method constructor"); |
| |
| CFCBase_decref((CFCBase*)klass); |
| } |
| |
| { |
| const char *class_src = |
| "inert class Rigor::Mortis nickname Mort {\n" |
| " inert void lie_still();\n" |
| "}\n"; |
| CFCClass *klass = CFCTest_parse_class(test, parser, class_src); |
| OK(test, CFCClass_inert(klass), |
| "inert modifier parsed and passed to constructor"); |
| |
| CFCBase_decref((CFCBase*)klass); |
| } |
| |
| { |
| const char *class_src = |
| "final class Ultimo {\n" |
| " /** Throws an error.\n" |
| " */\n" |
| " void Say_Never(Ultimo *self);\n" |
| "}\n"; |
| CFCClass *klass = CFCTest_parse_class(test, parser, class_src); |
| OK(test, CFCClass_final(klass), "final class_declaration"); |
| CFCBase_decref((CFCBase*)klass); |
| } |
| |
| CFCBase_decref((CFCBase*)parser); |
| CFCBase_decref((CFCBase*)neato); |
| CFCBase_decref((CFCBase*)file_spec); |
| CFCBase_decref((CFCBase*)thing_class); |
| CFCBase_decref((CFCBase*)widget_class); |
| CFCBase_decref((CFCBase*)thing); |
| CFCBase_decref((CFCBase*)widget); |
| CFCBase_decref((CFCBase*)tread_water); |
| CFCBase_decref((CFCBase*)foo); |
| CFCBase_decref((CFCBase*)foo_jr); |
| CFCBase_decref((CFCBase*)final_foo); |
| CFCBase_decref((CFCBase*)inert_foo); |
| CFCBase_decref((CFCBase*)do_stuff); |
| |
| CFCParcel_reap_singletons(); |
| } |
| |
| static int |
| S_has_symbol(CFCSymbol **symbols, const char *name) { |
| for (int i = 0; symbols[i]; ++i) { |
| if (strcmp(CFCSymbol_get_name(symbols[i]), name) == 0) { |
| return 1; |
| } |
| } |
| |
| return 0; |
| } |
| |