416 lines
14 KiB
OpenEdge ABL
416 lines
14 KiB
OpenEdge ABL
[Unit::] Unit Tests.
|
|
|
|
A selection of tests for, or demonstrations of, foundation features.
|
|
|
|
@h Strings.
|
|
|
|
=
|
|
void Unit::test_strings(void) {
|
|
text_stream *S = Str::new_from_wide_string(L"Jack and Jill");
|
|
PRINT("Setup: %S\n", S);
|
|
|
|
text_stream *T = Str::new_from_wide_string(L" had a great fall");
|
|
PRINT("Plus: %S\n", T);
|
|
Str::concatenate(S, T);
|
|
PRINT("Concatenation: %S\n", S);
|
|
|
|
text_stream *BB = Str::new_from_wide_string(L" banana bread is fun ");
|
|
PRINT("Setup statically: <%S>\n", BB);
|
|
Str::trim_white_space(BB);
|
|
PRINT("Trimmed: <%S>\n", BB);
|
|
|
|
Str::copy(BB, S);
|
|
PRINT("Copied: <%S>\n", BB);
|
|
|
|
PRINT("Length: %d\n", Str::len(BB));
|
|
|
|
Str::put(Str::at(BB, 3), L'Q');
|
|
PRINT("Modified: <%S>\n", BB);
|
|
|
|
text_stream *A = Str::new_from_wide_string(L"fish");
|
|
text_stream *B = Str::new_from_wide_string(L"Fish");
|
|
|
|
PRINT("%S eq %S? %d\n", A, B, Str::eq(A, B));
|
|
PRINT("%S ci-eq %S? %d\n", A, B, Str::eq_insensitive(A, B));
|
|
PRINT("%S ne %S? %d\n", A, B, Str::ne(A, B));
|
|
PRINT("%S ci-ne %S? %d\n", A, B, Str::ne_insensitive(A, B));
|
|
}
|
|
|
|
@h Literals.
|
|
|
|
=
|
|
void Unit::test_literals(void) {
|
|
LOG("This is \"tricky"); LOG("%S", I"bananas");
|
|
int z = '"'; LOG("%S%d", I"peaches", z);
|
|
text_stream *A = I"Jackdaws love my big sphinx of quartz";
|
|
PRINT("So A is <%S>\n", A);
|
|
text_stream *B = I"Jackdaws love my big sphinx of quartz";
|
|
PRINT("So B is <%S>\n", B);
|
|
text_stream *C = I"Jinxed wizards pluck ivy from my quilt";
|
|
PRINT("So C is <%S>\n", C);
|
|
if (A != B) PRINT("FAIL: A != B\n");
|
|
else PRINT("and A == B as pointers, too\n");
|
|
}
|
|
|
|
@h Dictionaries.
|
|
|
|
=
|
|
void Unit::test_dictionaries(text_stream *arg) {
|
|
dictionary *D = Dictionaries::new(2, TRUE);
|
|
Dictionaries::log(STDOUT, D);
|
|
filename *F = Filenames::from_text(arg);
|
|
TextFiles::read(F, FALSE, "unable to read file of test cases", TRUE,
|
|
&Unit::test_dictionaries_helper1, NULL, D);
|
|
Dictionaries::log(STDOUT, D);
|
|
TextFiles::read(F, FALSE, "unable to reread file of test cases", TRUE,
|
|
&Unit::test_dictionaries_helper2, NULL, D);
|
|
Dictionaries::log(STDOUT, D);
|
|
}
|
|
|
|
void Unit::test_dictionaries_helper1(text_stream *text, text_file_position *tfp, void *vD) {
|
|
dictionary *D = (dictionary *) vD;
|
|
match_results mr = Regexp::create_mr();
|
|
if (Regexp::match(&mr, text, L" *")) return;
|
|
if (Regexp::match(&mr, text, L"%'(%c*?)%' %'(%c*)%'")) {
|
|
if (Dictionaries::find(D, mr.exp[0]) == NULL) {
|
|
PRINT("Creating new entry <%S>\n", mr.exp[0]);
|
|
Dictionaries::create_text(D, mr.exp[0]);
|
|
if (Dictionaries::find(D, mr.exp[0]) == NULL) PRINT("Didn't create\n");
|
|
}
|
|
Str::copy(Dictionaries::get_text(D, mr.exp[0]), mr.exp[1]);
|
|
if (!Str::eq(mr.exp[1], Dictionaries::get_text(D, mr.exp[0])))
|
|
PRINT("FAIL: can't read back entry once written\n");
|
|
Regexp::dispose_of(&mr);
|
|
return;
|
|
}
|
|
Errors::in_text_file("test case won't parse", tfp);
|
|
}
|
|
|
|
void Unit::test_dictionaries_helper2(text_stream *text, text_file_position *tfp, void *vD) {
|
|
dictionary *D = (dictionary *) vD;
|
|
match_results mr = Regexp::create_mr();
|
|
if (Regexp::match(&mr, text, L" *")) return;
|
|
if (Regexp::match(&mr, text, L"%'(%c*?)%' %'(%c*)%'")) {
|
|
if (Dictionaries::find(D, mr.exp[0]) == NULL) {
|
|
PRINT("Missing %S\n", mr.exp[0]);
|
|
} else {
|
|
Dictionaries::destroy(D, mr.exp[0]);
|
|
if (Dictionaries::find(D, mr.exp[0])) PRINT("Didn't destroy\n");
|
|
}
|
|
Regexp::dispose_of(&mr);
|
|
return;
|
|
}
|
|
Errors::in_text_file("test case won't parse", tfp);
|
|
}
|
|
|
|
@h Regexp.
|
|
|
|
=
|
|
void Unit::test_regexp(text_stream *arg) {
|
|
filename *F = Filenames::from_text(arg);
|
|
TextFiles::read(F, FALSE, "unable to read file of test cases", TRUE,
|
|
&Unit::test_regexp_helper, NULL, NULL);
|
|
}
|
|
|
|
void Unit::test_regexp_helper(text_stream *text, text_file_position *tfp, void *state) {
|
|
match_results mr = Regexp::create_mr();
|
|
if (Regexp::match(&mr, text, L" *")) return;
|
|
if (Regexp::match(&mr, text, L"%'(%c*?)%' %'(%c*)%'")) {
|
|
wchar_t pattern[1024];
|
|
Str::copy_to_wide_string(pattern, mr.exp[1], 1024);
|
|
match_results mr2 = Regexp::create_mr();
|
|
PRINT("Text <%S> pattern <%w>: ", mr.exp[0], pattern);
|
|
if (Regexp::match(&mr2, mr.exp[0], pattern)) {
|
|
PRINT("Match");
|
|
for (int i=0; i<mr2.no_matched_texts; i++)
|
|
PRINT(" %d=<%S>", i, mr2.exp[i]);
|
|
PRINT("\n");
|
|
Regexp::dispose_of(&mr2);
|
|
} else {
|
|
PRINT("No match\n");
|
|
}
|
|
Regexp::dispose_of(&mr);
|
|
return;
|
|
}
|
|
Errors::in_text_file("test case won't parse", tfp);
|
|
}
|
|
|
|
@h Replacements.
|
|
|
|
=
|
|
void Unit::test_replacement(text_stream *arg) {
|
|
filename *F = Filenames::from_text(arg);
|
|
TextFiles::read(F, FALSE, "unable to read file of test cases", TRUE,
|
|
&Unit::test_replacement_helper, NULL, NULL);
|
|
}
|
|
|
|
void Unit::test_replacement_helper(text_stream *text, text_file_position *tfp, void *state) {
|
|
match_results mr = Regexp::create_mr();
|
|
if (Regexp::match(&mr, text, L" *")) return;
|
|
if (Regexp::match(&mr, text, L"%'(%c*?)%' %'(%c*?)%' %'(%c*)%'")) {
|
|
wchar_t pattern[1024];
|
|
wchar_t replacement[1024];
|
|
Str::copy_to_wide_string(pattern, mr.exp[1], 1024);
|
|
Str::copy_to_wide_string(replacement, mr.exp[2], 1024);
|
|
PRINT("Text <%S> pattern <%w> replacement <%w>: ", mr.exp[0], pattern, replacement);
|
|
int rc = Regexp::replace(mr.exp[0], pattern, replacement, REP_REPEATING);
|
|
PRINT("%S (%d replacement%s)\n", mr.exp[0], rc, (rc == 1)?"":"s");
|
|
Regexp::dispose_of(&mr);
|
|
return;
|
|
}
|
|
Errors::in_text_file("test case won't parse", tfp);
|
|
}
|
|
|
|
@h Linked lists.
|
|
|
|
=
|
|
void Unit::test_linked_lists(void) {
|
|
linked_list *test_list = NEW_LINKED_LIST(text_stream);
|
|
PRINT("List (which should be empty) contains:\n");
|
|
text_stream *text;
|
|
LOOP_OVER_LINKED_LIST(text, text_stream, test_list) {
|
|
PRINT("%S\n", text);
|
|
}
|
|
for (int i = 1; i<17; i++) {
|
|
TEMPORARY_TEXT(T)
|
|
WRITE_TO(T, "S%d", i);
|
|
ADD_TO_LINKED_LIST(Str::duplicate(T), text_stream, test_list);
|
|
DISCARD_TEXT(T)
|
|
}
|
|
PRINT("List contains:\n");
|
|
LOOP_OVER_LINKED_LIST(text, text_stream, test_list) {
|
|
PRINT("%S\n", text);
|
|
}
|
|
PRINT("And has length %d\n", LinkedLists::len(test_list));
|
|
PRINT("First is: %S\n", FIRST_IN_LINKED_LIST(text_stream, test_list));
|
|
PRINT("Last is: %S\n", LAST_IN_LINKED_LIST(text_stream, test_list));
|
|
}
|
|
|
|
@h Stacks.
|
|
|
|
=
|
|
void Unit::test_stacks(void) {
|
|
lifo_stack *test_stack = NEW_LIFO_STACK(text_stream);
|
|
PRINT("Top of stack is: %S\n", TOP_OF_LIFO_STACK(text_stream, test_stack));
|
|
if (LIFO_STACK_EMPTY(text_stream, test_stack)) PRINT("Stack is empty\n");
|
|
PUSH_TO_LIFO_STACK(I"Mercury", text_stream, test_stack);
|
|
PRINT("Top of stack is: %S\n", TOP_OF_LIFO_STACK(text_stream, test_stack));
|
|
if (LIFO_STACK_EMPTY(text_stream, test_stack)) PRINT("Stack is empty\n");
|
|
PUSH_TO_LIFO_STACK(I"Venus", text_stream, test_stack);
|
|
PRINT("Top of stack is: %S\n", TOP_OF_LIFO_STACK(text_stream, test_stack));
|
|
if (LIFO_STACK_EMPTY(text_stream, test_stack)) PRINT("Stack is empty\n");
|
|
POP_LIFO_STACK(text_stream, test_stack);
|
|
PRINT("Top of stack is: %S\n", TOP_OF_LIFO_STACK(text_stream, test_stack));
|
|
if (LIFO_STACK_EMPTY(text_stream, test_stack)) PRINT("Stack is empty\n");
|
|
PUSH_TO_LIFO_STACK(I"Earth", text_stream, test_stack);
|
|
PRINT("Top of stack is: %S\n", TOP_OF_LIFO_STACK(text_stream, test_stack));
|
|
if (LIFO_STACK_EMPTY(text_stream, test_stack)) PRINT("Stack is empty\n");
|
|
POP_LIFO_STACK(text_stream, test_stack);
|
|
PRINT("Top of stack is: %S\n", TOP_OF_LIFO_STACK(text_stream, test_stack));
|
|
if (LIFO_STACK_EMPTY(text_stream, test_stack)) PRINT("Stack is empty\n");
|
|
POP_LIFO_STACK(text_stream, test_stack);
|
|
PRINT("Top of stack is: %S\n", TOP_OF_LIFO_STACK(text_stream, test_stack));
|
|
if (LIFO_STACK_EMPTY(text_stream, test_stack)) PRINT("Stack is empty\n");
|
|
}
|
|
|
|
@h Semantic versions.
|
|
|
|
=
|
|
void Unit::test_range(OUTPUT_STREAM, text_stream *text) {
|
|
semantic_version_number V = VersionNumbers::from_text(text);
|
|
semver_range *R = VersionNumberRanges::compatibility_range(V);
|
|
WRITE("Compatibility range of %v = ", &V);
|
|
VersionNumberRanges::write_range(OUT, R);
|
|
WRITE("\n");
|
|
R = VersionNumberRanges::at_least_range(V);
|
|
WRITE("At-least range of %v = ", &V);
|
|
VersionNumberRanges::write_range(OUT, R);
|
|
WRITE("\n");
|
|
R = VersionNumberRanges::at_most_range(V);
|
|
WRITE("At-most range of %v = ", &V);
|
|
VersionNumberRanges::write_range(OUT, R);
|
|
WRITE("\n");
|
|
}
|
|
|
|
void Unit::test_intersect(OUTPUT_STREAM,
|
|
text_stream *text1, int r1, text_stream *text2, int r2) {
|
|
semantic_version_number V1 = VersionNumbers::from_text(text1);
|
|
semver_range *R1 = NULL;
|
|
if (r1 == 0) R1 = VersionNumberRanges::compatibility_range(V1);
|
|
else if (r1 > 0) R1 = VersionNumberRanges::at_least_range(V1);
|
|
else if (r1 < 0) R1 = VersionNumberRanges::at_most_range(V1);
|
|
semantic_version_number V2 = VersionNumbers::from_text(text2);
|
|
semver_range *R2 = NULL;
|
|
if (r2 == 0) R2 = VersionNumberRanges::compatibility_range(V2);
|
|
else if (r2 > 0) R2 = VersionNumberRanges::at_least_range(V2);
|
|
else if (r2 < 0) R2 = VersionNumberRanges::at_most_range(V2);
|
|
VersionNumberRanges::write_range(OUT, R1);
|
|
WRITE(" intersect ");
|
|
VersionNumberRanges::write_range(OUT, R2);
|
|
WRITE(" = ");
|
|
int changed = VersionNumberRanges::intersect_range(R1, R2);
|
|
VersionNumberRanges::write_range(OUT, R1);
|
|
if (changed) WRITE (" -- changed");
|
|
WRITE("\n");
|
|
}
|
|
|
|
void Unit::test_read_write(OUTPUT_STREAM, text_stream *text) {
|
|
semantic_version_number V = VersionNumbers::from_text(text);
|
|
WRITE("'%S' --> %v\n", text, &V);
|
|
}
|
|
|
|
void Unit::test_precedence(OUTPUT_STREAM, text_stream *text1, text_stream *text2) {
|
|
semantic_version_number V1 = VersionNumbers::from_text(text1);
|
|
semantic_version_number V2 = VersionNumbers::from_text(text2);
|
|
int gt = VersionNumbers::gt(V1, V2);
|
|
int eq = VersionNumbers::eq(V1, V2);
|
|
int lt = VersionNumbers::lt(V1, V2);
|
|
if (lt) WRITE("%v < %v", &V1, &V2);
|
|
if (eq) WRITE("%v = %v", &V1, &V2);
|
|
if (gt) WRITE("%v > %v", &V1, &V2);
|
|
WRITE("\n");
|
|
}
|
|
|
|
void Unit::test_semver(void) {
|
|
Unit::test_read_write(STDOUT, I"1");
|
|
Unit::test_read_write(STDOUT, I"1.2");
|
|
Unit::test_read_write(STDOUT, I"1.2.3");
|
|
Unit::test_read_write(STDOUT, I"71.0.45672");
|
|
Unit::test_read_write(STDOUT, I"1.2.3.4");
|
|
Unit::test_read_write(STDOUT, I"9/861022");
|
|
Unit::test_read_write(STDOUT, I"9/86102");
|
|
Unit::test_read_write(STDOUT, I"9/8610223");
|
|
Unit::test_read_write(STDOUT, I"9/861022.2");
|
|
Unit::test_read_write(STDOUT, I"9/861022/2");
|
|
Unit::test_read_write(STDOUT, I"1.2.3-alpha.0.x45.1789");
|
|
Unit::test_read_write(STDOUT, I"1+lobster");
|
|
Unit::test_read_write(STDOUT, I"1.2+lobster");
|
|
Unit::test_read_write(STDOUT, I"1.2.3+lobster");
|
|
Unit::test_read_write(STDOUT, I"1.2.3-beta.2+shellfish");
|
|
|
|
PRINT("\n");
|
|
Unit::test_precedence(STDOUT, I"3", I"5");
|
|
Unit::test_precedence(STDOUT, I"3", I"3");
|
|
Unit::test_precedence(STDOUT, I"3", I"3.0");
|
|
Unit::test_precedence(STDOUT, I"3", I"3.0.0");
|
|
Unit::test_precedence(STDOUT, I"3.1.41", I"3.1.5");
|
|
Unit::test_precedence(STDOUT, I"3.1.41", I"3.2.5");
|
|
Unit::test_precedence(STDOUT, I"3.1.41", I"3.1.41+arm64");
|
|
Unit::test_precedence(STDOUT, I"3.1.41", I"3.1.41-pre.0.1");
|
|
Unit::test_precedence(STDOUT, I"3.1.41-alpha.72", I"3.1.41-alpha.8");
|
|
Unit::test_precedence(STDOUT, I"3.1.41-alpha.72a", I"3.1.41-alpha.8a");
|
|
Unit::test_precedence(STDOUT, I"3.1.41-alpha.72", I"3.1.41-beta.72");
|
|
Unit::test_precedence(STDOUT, I"3.1.41-alpha.72", I"3.1.41-alpha.72.zeta");
|
|
Unit::test_precedence(STDOUT, I"1.2.3+lobster.54", I"1.2.3+lobster.100");
|
|
|
|
PRINT("\n");
|
|
Unit::test_range(STDOUT, I"6.4.2-kappa.17");
|
|
|
|
PRINT("\n");
|
|
Unit::test_intersect(STDOUT, I"6.4.2-kappa.17", 0, I"3.5.5", 0);
|
|
Unit::test_intersect(STDOUT, I"6.4.2-kappa.17", 0, I"6.9.1", 0);
|
|
Unit::test_intersect(STDOUT, I"6.9.1", 0, I"6.4.2-kappa.17", 0);
|
|
Unit::test_intersect(STDOUT, I"6.4.2", 1, I"3.5.5", 1);
|
|
Unit::test_intersect(STDOUT, I"6.4.2", 1, I"3.5.5", -1);
|
|
Unit::test_intersect(STDOUT, I"6.4.2", -1, I"3.5.5", 1);
|
|
Unit::test_intersect(STDOUT, I"6.4.2", -1, I"3.5.5", -1);
|
|
}
|
|
|
|
@h Trees.
|
|
|
|
@e prince_CLASS
|
|
@e princess_CLASS
|
|
|
|
=
|
|
DECLARE_CLASS(prince)
|
|
DECLARE_CLASS(princess)
|
|
|
|
@ =
|
|
typedef struct prince {
|
|
struct text_stream *boys_name;
|
|
CLASS_DEFINITION
|
|
} prince;
|
|
|
|
typedef struct princess {
|
|
int meaningless;
|
|
struct text_stream *girls_name;
|
|
CLASS_DEFINITION
|
|
} princess;
|
|
|
|
tree_node_type *M = NULL, *F = NULL;
|
|
|
|
@ =
|
|
void Unit::test_trees(void) {
|
|
tree_type *TT = Trees::new_type(I"royal family", &Unit::verifier);
|
|
heterogeneous_tree *royalty = Trees::new(TT);
|
|
M = Trees::new_node_type(I"male", prince_CLASS, &Unit::prince_verifier);
|
|
F = Trees::new_node_type(I"female", princess_CLASS, &Unit::princess_verifier);
|
|
|
|
prince *charles_I = CREATE(prince);
|
|
charles_I->boys_name = I"Charles I of England";
|
|
princess *mary = CREATE(princess);
|
|
mary->girls_name = I"Mary, Princess Royal";
|
|
prince *charles_II = CREATE(prince);
|
|
charles_II->boys_name = I"Charles II of England";
|
|
prince *james_II = CREATE(prince);
|
|
james_II->boys_name = I"James II of England";
|
|
|
|
tree_node *charles_I_n = Trees::new_node(royalty, M, STORE_POINTER_prince(charles_I));
|
|
tree_node *charles_II_n = Trees::new_node(royalty, M, STORE_POINTER_prince(charles_II));
|
|
tree_node *james_II_n = Trees::new_node(royalty, M, STORE_POINTER_prince(james_II));
|
|
tree_node *mary_n = Trees::new_node(royalty, F, STORE_POINTER_princess(mary));
|
|
|
|
Unit::show_tree(STDOUT, royalty);
|
|
Trees::make_root(royalty, charles_I_n);
|
|
Unit::show_tree(STDOUT, royalty);
|
|
Trees::make_child(charles_II_n, charles_I_n);
|
|
Unit::show_tree(STDOUT, royalty);
|
|
Trees::make_eldest_child(mary_n, charles_I_n);
|
|
Trees::make_child(james_II_n, charles_I_n);
|
|
Unit::show_tree(STDOUT, royalty);
|
|
}
|
|
|
|
int Unit::verifier(tree_node *N) {
|
|
if (N->type == M) PRINT("(Root is M)\n");
|
|
if (N->type == F) PRINT("(Root is F)\n");
|
|
if (N->type == M) return TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
int Unit::prince_verifier(tree_node *N) {
|
|
for (tree_node *C = N->child; C; C = C->next)
|
|
if (C->type == M) PRINT("(Prince's child is M)\n");
|
|
else PRINT("(Prince's child is F)\n");
|
|
PRINT("(verified)\n");
|
|
return TRUE;
|
|
}
|
|
|
|
int Unit::princess_verifier(tree_node *N) {
|
|
for (tree_node *C = N->child; C; C = C->next)
|
|
if (C->type == M) PRINT("(Princess's child is M)\n");
|
|
else PRINT("(Princess's child is F)\n");
|
|
PRINT("(verified)\n");
|
|
return TRUE;
|
|
}
|
|
|
|
@ =
|
|
void Unit::show_tree(text_stream *OUT, heterogeneous_tree *T) {
|
|
WRITE("%S\n", T->type->name);
|
|
INDENT;
|
|
Trees::traverse_from(T->root, &Unit::visit, (void *) STDOUT, 0);
|
|
OUTDENT;
|
|
WRITE("Done\n");
|
|
}
|
|
|
|
int Unit::visit(tree_node *N, void *state, int L) {
|
|
text_stream *OUT = (text_stream *) state;
|
|
for (int i=0; i<L; i++) WRITE(" ");
|
|
if (N->type == M) {
|
|
prince *P = RETRIEVE_POINTER_prince(N->content);
|
|
WRITE("Male: %S\n", P->boys_name);
|
|
} else if (N->type == F) {
|
|
princess *P = RETRIEVE_POINTER_princess(N->content);
|
|
WRITE("Female: %S\n", P->girls_name);
|
|
} else WRITE("Unknown node\n");
|
|
return TRUE;
|
|
}
|