diff --git a/CHANGES b/CHANGES index 6016eea77f..54306ae516 100644 --- a/CHANGES +++ b/CHANGES @@ -1,4 +1,9 @@ +2.5-802 | 2018-08-02 10:40:36 -0500 + + * Add set operations: union, intersection, difference, comparison + (Vern Paxson, Corelight) + 2.5-796 | 2018-08-01 16:31:25 -0500 * Add 'W' connection history indicator for zero windows diff --git a/NEWS b/NEWS index 99d8c0e01e..6af577eb0d 100644 --- a/NEWS +++ b/NEWS @@ -300,6 +300,15 @@ New Functionality event tcp_multiple_retransmissions(c: connection, is_orig: bool, threshold: count); +- Added support for set union, intersection, difference, and comparison + operations. The corresponding operators for the first three are + "s1 | s2", "s1 & s2", and "s1 - s2". Relationals are in terms + of subsets, so "s1 < s2" yields true if s1 is a proper subset of s2 + and "s1 == s2" if the two sets have exactly the same elements. + "s1 <= s2" holds for subsets or equality, and similarly "s1 != s2", + "s1 > s2", and "s1 >= s2" have the expected meanings in terms + of non-equality, proper superset, and superset-or-equal. + Changed Functionality --------------------- diff --git a/VERSION b/VERSION index c360bd2ca6..78ef01e6f4 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.5-796 +2.5-802 diff --git a/doc/script-reference/types.rst b/doc/script-reference/types.rst index f1d81f63ab..9de2bcda0f 100644 --- a/doc/script-reference/types.rst +++ b/doc/script-reference/types.rst @@ -544,6 +544,15 @@ Here is a more detailed description of each type: |s| + You can compute the union, intersection, or difference of two sets + using the ``|``, ``&``, and ``-`` operators. You can compare + sets for equality (they have exactly the same elements) using ``==``. + The ``<`` operator returns ``T`` if the lefthand operand is a proper + subset of the righthand operand. Similarly, ``<=`` returns ``T`` + if the lefthand operator is a subset (not necessarily proper, i.e., + it may be equal to the righthand operand). The operators ``!=``, ``>`` + and ``>=`` provide the expected complementary operations. + See the :bro:keyword:`for` statement for info on how to iterate over the elements in a set. diff --git a/src/Expr.cc b/src/Expr.cc index a86f86b9c4..d24bd25624 100644 --- a/src/Expr.cc +++ b/src/Expr.cc @@ -672,6 +672,9 @@ Val* BinaryExpr::Fold(Val* v1, Val* v2) const if ( v1->Type()->Tag() == TYPE_PATTERN ) return PatternFold(v1, v2); + if ( v1->Type()->IsSet() ) + return SetFold(v1, v2); + if ( it == TYPE_INTERNAL_ADDR ) return AddrFold(v1, v2); @@ -858,6 +861,7 @@ Val* BinaryExpr::StringFold(Val* v1, Val* v2) const return new Val(result, TYPE_BOOL); } + Val* BinaryExpr::PatternFold(Val* v1, Val* v2) const { const RE_Matcher* re1 = v1->AsPattern(); @@ -873,6 +877,61 @@ Val* BinaryExpr::PatternFold(Val* v1, Val* v2) const return new PatternVal(res); } +Val* BinaryExpr::SetFold(Val* v1, Val* v2) const + { + TableVal* tv1 = v1->AsTableVal(); + TableVal* tv2 = v2->AsTableVal(); + TableVal* result; + bool res; + + switch ( tag ) { + case EXPR_AND: + return tv1->Intersect(tv2); + + case EXPR_OR: + result = v1->Clone()->AsTableVal(); + + if ( ! tv2->AddTo(result, false, false) ) + reporter->InternalError("set union failed to type check"); + return result; + + case EXPR_SUB: + result = v1->Clone()->AsTableVal(); + + if ( ! tv2->RemoveFrom(result) ) + reporter->InternalError("set difference failed to type check"); + return result; + + case EXPR_EQ: + res = tv1->EqualTo(tv2); + break; + + case EXPR_NE: + res = ! tv1->EqualTo(tv2); + break; + + case EXPR_LT: + res = tv1->IsSubsetOf(tv2) && tv1->Size() < tv2->Size(); + break; + + case EXPR_LE: + res = tv1->IsSubsetOf(tv2); + break; + + case EXPR_GE: + case EXPR_GT: + // These should't happen due to canonicalization. + reporter->InternalError("confusion over canonicalization in set comparison"); + break; + + default: + BadTag("BinaryExpr::SetFold", expr_name(tag)); + return 0; + } + + return new Val(res, TYPE_BOOL); + } + Val* BinaryExpr::AddrFold(Val* v1, Val* v2) const { IPAddr a1 = v1->AsAddr(); @@ -1454,24 +1513,39 @@ SubExpr::SubExpr(Expr* arg_op1, Expr* arg_op2) if ( IsError() ) return; - TypeTag bt1 = op1->Type()->Tag(); - if ( IsVector(bt1) ) - bt1 = op1->Type()->AsVectorType()->YieldType()->Tag(); + const BroType* t1 = op1->Type(); + const BroType* t2 = op2->Type(); - TypeTag bt2 = op2->Type()->Tag(); + TypeTag bt1 = t1->Tag(); + if ( IsVector(bt1) ) + bt1 = t1->AsVectorType()->YieldType()->Tag(); + + TypeTag bt2 = t2->Tag(); if ( IsVector(bt2) ) - bt2 = op2->Type()->AsVectorType()->YieldType()->Tag(); + bt2 = t2->AsVectorType()->YieldType()->Tag(); BroType* base_result_type = 0; if ( bt1 == TYPE_TIME && bt2 == TYPE_INTERVAL ) base_result_type = base_type(bt1); + else if ( bt1 == TYPE_TIME && bt2 == TYPE_TIME ) SetType(base_type(TYPE_INTERVAL)); + else if ( bt1 == TYPE_INTERVAL && bt2 == TYPE_INTERVAL ) base_result_type = base_type(bt1); + + else if ( t1->IsSet() && t2->IsSet() ) + { + if ( same_type(t1, t2) ) + SetType(op1->Type()->Ref()); + else + ExprError("incompatible \"set\" operands"); + } + else if ( BothArithmetic(bt1, bt2) ) PromoteType(max_type(bt1, bt2), is_vector(op1) || is_vector(op2)); + else ExprError("requires arithmetic operands"); @@ -1888,13 +1962,16 @@ BitExpr::BitExpr(BroExprTag arg_tag, Expr* arg_op1, Expr* arg_op2) if ( IsError() ) return; - TypeTag bt1 = op1->Type()->Tag(); - if ( IsVector(bt1) ) - bt1 = op1->Type()->AsVectorType()->YieldType()->Tag(); + const BroType* t1 = op1->Type(); + const BroType* t2 = op2->Type(); - TypeTag bt2 = op2->Type()->Tag(); + TypeTag bt1 = t1->Tag(); + if ( IsVector(bt1) ) + bt1 = t1->AsVectorType()->YieldType()->Tag(); + + TypeTag bt2 = t2->Tag(); if ( IsVector(bt2) ) - bt2 = op2->Type()->AsVectorType()->YieldType()->Tag(); + bt2 = t2->AsVectorType()->YieldType()->Tag(); if ( (bt1 == TYPE_COUNT || bt1 == TYPE_COUNTER) && (bt2 == TYPE_COUNT || bt2 == TYPE_COUNTER) ) @@ -1917,8 +1994,16 @@ BitExpr::BitExpr(BroExprTag arg_tag, Expr* arg_op1, Expr* arg_op2) SetType(base_type(TYPE_PATTERN)); } + else if ( t1->IsSet() && t2->IsSet() ) + { + if ( same_type(t1, t2) ) + SetType(op1->Type()->Ref()); + else + ExprError("incompatible \"set\" operands"); + } + else - ExprError("requires \"count\" operands"); + ExprError("requires \"count\" or compatible \"set\" operands"); } IMPLEMENT_SERIAL(BitExpr, SER_BIT_EXPR); @@ -1943,13 +2028,16 @@ EqExpr::EqExpr(BroExprTag arg_tag, Expr* arg_op1, Expr* arg_op2) Canonicize(); - TypeTag bt1 = op1->Type()->Tag(); - if ( IsVector(bt1) ) - bt1 = op1->Type()->AsVectorType()->YieldType()->Tag(); + const BroType* t1 = op1->Type(); + const BroType* t2 = op2->Type(); - TypeTag bt2 = op2->Type()->Tag(); + TypeTag bt1 = t1->Tag(); + if ( IsVector(bt1) ) + bt1 = t1->AsVectorType()->YieldType()->Tag(); + + TypeTag bt2 = t2->Tag(); if ( IsVector(bt2) ) - bt2 = op2->Type()->AsVectorType()->YieldType()->Tag(); + bt2 = t2->AsVectorType()->YieldType()->Tag(); if ( is_vector(op1) || is_vector(op2) ) SetType(new VectorType(base_type(TYPE_BOOL))); @@ -1979,10 +2067,20 @@ EqExpr::EqExpr(BroExprTag arg_tag, Expr* arg_op1, Expr* arg_op2) break; case TYPE_ENUM: - if ( ! same_type(op1->Type(), op2->Type()) ) + if ( ! same_type(t1, t2) ) ExprError("illegal enum comparison"); break; + case TYPE_TABLE: + if ( t1->IsSet() && t2->IsSet() ) + { + if ( ! same_type(t1, t2) ) + ExprError("incompatible sets in comparison"); + break; + } + + // FALL THROUGH + default: ExprError("illegal comparison"); } @@ -2045,13 +2143,16 @@ RelExpr::RelExpr(BroExprTag arg_tag, Expr* arg_op1, Expr* arg_op2) Canonicize(); - TypeTag bt1 = op1->Type()->Tag(); - if ( IsVector(bt1) ) - bt1 = op1->Type()->AsVectorType()->YieldType()->Tag(); + const BroType* t1 = op1->Type(); + const BroType* t2 = op2->Type(); - TypeTag bt2 = op2->Type()->Tag(); + TypeTag bt1 = t1->Tag(); + if ( IsVector(bt1) ) + bt1 = t1->AsVectorType()->YieldType()->Tag(); + + TypeTag bt2 = t2->Tag(); if ( IsVector(bt2) ) - bt2 = op2->Type()->AsVectorType()->YieldType()->Tag(); + bt2 = t2->AsVectorType()->YieldType()->Tag(); if ( is_vector(op1) || is_vector(op2) ) SetType(new VectorType(base_type(TYPE_BOOL))); @@ -2061,6 +2162,12 @@ RelExpr::RelExpr(BroExprTag arg_tag, Expr* arg_op1, Expr* arg_op2) if ( BothArithmetic(bt1, bt2) ) PromoteOps(max_type(bt1, bt2)); + else if ( t1->IsSet() && t2->IsSet() ) + { + if ( ! same_type(t1, t2) ) + ExprError("incompatible sets in comparison"); + } + else if ( bt1 != bt2 ) ExprError("operands must be of the same type"); diff --git a/src/Expr.h b/src/Expr.h index c21547d91c..8ac547c534 100644 --- a/src/Expr.h +++ b/src/Expr.h @@ -332,6 +332,9 @@ protected: // Same for when the constants are patterns. virtual Val* PatternFold(Val* v1, Val* v2) const; + // Same for when the constants are sets. + virtual Val* SetFold(Val* v1, Val* v2) const; + // Same for when the constants are addresses or subnets. virtual Val* AddrFold(Val* v1, Val* v2) const; virtual Val* SubNetFold(Val* v1, Val* v2) const; diff --git a/src/Val.cc b/src/Val.cc index 36269ad9c9..1024251418 100644 --- a/src/Val.cc +++ b/src/Val.cc @@ -1706,9 +1706,11 @@ int TableVal::RemoveFrom(Val* val) const HashKey* k; while ( tbl->NextEntry(k, c) ) { - Val* index = RecoverIndex(k); - - Unref(index); + // Not sure that this is 100% sound, since the HashKey + // comes from one table but is being used in another. + // OTOH, they are both the same type, so as long as + // we don't have hash keys that are keyed per dictionary, + // it should work ... Unref(t->Delete(k)); delete k; } @@ -1716,6 +1718,91 @@ int TableVal::RemoveFrom(Val* val) const return 1; } +TableVal* TableVal::Intersect(const TableVal* tv) const + { + TableVal* result = new TableVal(table_type); + + const PDict(TableEntryVal)* t0 = AsTable(); + const PDict(TableEntryVal)* t1 = tv->AsTable(); + PDict(TableEntryVal)* t2 = result->AsNonConstTable(); + + // Figure out which is smaller; assign it to t1. + if ( t1->Length() > t0->Length() ) + { // Swap. + const PDict(TableEntryVal)* tmp = t1; + t1 = t0; + t0 = tmp; + } + + IterCookie* c = t1->InitForIteration(); + HashKey* k; + while ( t1->NextEntry(k, c) ) + { + // Here we leverage the same assumption about consistent + // hashes as in TableVal::RemoveFrom above. + if ( t0->Lookup(k) ) + t2->Insert(k, new TableEntryVal(0)); + + delete k; + } + + return result; + } + +bool TableVal::EqualTo(const TableVal* tv) const + { + const PDict(TableEntryVal)* t0 = AsTable(); + const PDict(TableEntryVal)* t1 = tv->AsTable(); + + if ( t0->Length() != t1->Length() ) + return false; + + IterCookie* c = t0->InitForIteration(); + HashKey* k; + while ( t0->NextEntry(k, c) ) + { + // Here we leverage the same assumption about consistent + // hashes as in TableVal::RemoveFrom above. + if ( ! t1->Lookup(k) ) + { + delete k; + t0->StopIteration(c); + return false; + } + + delete k; + } + + return true; + } + +bool TableVal::IsSubsetOf(const TableVal* tv) const + { + const PDict(TableEntryVal)* t0 = AsTable(); + const PDict(TableEntryVal)* t1 = tv->AsTable(); + + if ( t0->Length() > t1->Length() ) + return false; + + IterCookie* c = t0->InitForIteration(); + HashKey* k; + while ( t0->NextEntry(k, c) ) + { + // Here we leverage the same assumption about consistent + // hashes as in TableVal::RemoveFrom above. + if ( ! t1->Lookup(k) ) + { + delete k; + t0->StopIteration(c); + return false; + } + + delete k; + } + + return true; + } + int TableVal::ExpandAndInit(Val* index, Val* new_val) { BroType* index_type = index->Type(); diff --git a/src/Val.h b/src/Val.h index 771ed40dd1..32ce6a0187 100644 --- a/src/Val.h +++ b/src/Val.h @@ -809,6 +809,22 @@ public: // Returns true if the addition typechecked, false if not. int RemoveFrom(Val* v) const override; + // Returns a new table that is the intersection of this + // table and the given table. Intersection is just done + // on index, not on yield value, so this really only makes + // sense for sets. + TableVal* Intersect(const TableVal* v) const; + + // Returns true if this set contains the same members as the + // given set. Note that comparisons are done using hash keys, + // so errors can arise for compound sets such as sets-of-sets. + // See https://bro-tracker.atlassian.net/browse/BIT-1949. + bool EqualTo(const TableVal* v) const; + + // Returns true if this set is a subset (not necessarily proper) + // of the given set. + bool IsSubsetOf(const TableVal* v) const; + // Expands any lists in the index into multiple initializations. // Returns true if the initializations typecheck, false if not. int ExpandAndInit(Val* index, Val* new_val); diff --git a/testing/btest/Baseline/language.set/out b/testing/btest/Baseline/language.set/out index fc157cf7d9..0128420cbf 100644 --- a/testing/btest/Baseline/language.set/out +++ b/testing/btest/Baseline/language.set/out @@ -42,3 +42,30 @@ remove element (PASS) !in operator (PASS) remove element (PASS) !in operator (PASS) +union (PASS) +intersection (FAIL) +difference (PASS) +difference (PASS) +union/inter. (PASS) +relational (PASS) +relational (PASS) +subset (FAIL) +subset (FAIL) +subset (PASS) +superset (FAIL) +superset (FAIL) +superset (FAIL) +superset (PASS) +non-ordering (FAIL) +non-ordering (PASS) +superset (PASS) +superset (FAIL) +superset (PASS) +superset (PASS) +superset (PASS) +superset (FAIL) +equality (PASS) +equality (FAIL) +non-equality (PASS) +equality (FAIL) +magnitude (FAIL) diff --git a/testing/btest/core/leaks/set.bro b/testing/btest/core/leaks/set.bro new file mode 100644 index 0000000000..b3f2200d28 --- /dev/null +++ b/testing/btest/core/leaks/set.bro @@ -0,0 +1,194 @@ +# @TEST-GROUP: leaks +# @TEST-REQUIRES: bro --help 2>&1 | grep -q mem-leaks + +# @TEST-EXEC: HEAP_CHECK_DUMP_DIRECTORY=. HEAPCHECK=local btest-bg-run bro bro -m -b -r $TRACES/http/get.trace %INPUT +# @TEST-EXEC: btest-bg-wait 60 + +function test_case(msg: string, expect: bool) + { + print fmt("%s (%s)", msg, expect ? "PASS" : "FAIL"); + } + +# Note: only global sets can be initialized with curly braces +global sg1: set[string] = { "curly", "braces" }; +global sg2: set[port, string, bool] = { [10/udp, "curly", F], + [11/udp, "braces", T] }; +global sg3 = { "more", "curly", "braces" }; + +global did_once = F; + +event new_connection(cc: connection) + { + if ( did_once ) + return; + + did_once = T; + + local s1: set[string] = set( "test", "example" ); + local s2: set[string] = set(); + local s3: set[string]; + local s4 = set( "type inference" ); + local s5: set[port, string, bool] = set( [1/tcp, "test", T], + [2/tcp, "example", F] ); + local s6: set[port, string, bool] = set(); + local s7: set[port, string, bool]; + local s8 = set( [8/tcp, "type inference", T] ); + + # Type inference tests + + test_case( "type inference", type_name(s4) == "set[string]" ); + test_case( "type inference", type_name(s8) == "set[port,string,bool]" ); + test_case( "type inference", type_name(sg3) == "set[string]" ); + + # Test the size of each set + + test_case( "cardinality", |s1| == 2 ); + test_case( "cardinality", |s2| == 0 ); + test_case( "cardinality", |s3| == 0 ); + test_case( "cardinality", |s4| == 1 ); + test_case( "cardinality", |s5| == 2 ); + test_case( "cardinality", |s6| == 0 ); + test_case( "cardinality", |s7| == 0 ); + test_case( "cardinality", |s8| == 1 ); + test_case( "cardinality", |sg1| == 2 ); + test_case( "cardinality", |sg2| == 2 ); + test_case( "cardinality", |sg3| == 3 ); + + # Test iterating over each set + + local ct: count; + ct = 0; + for ( c in s1 ) + { + if ( type_name(c) != "string" ) + print "Error: wrong set element type"; + ++ct; + } + test_case( "iterate over set", ct == 2 ); + + ct = 0; + for ( c in s2 ) + { + ++ct; + } + test_case( "iterate over set", ct == 0 ); + + ct = 0; + for ( [c1,c2,c3] in s5 ) + { + ++ct; + } + test_case( "iterate over set", ct == 2 ); + + ct = 0; + for ( [c1,c2,c3] in sg2 ) + { + ++ct; + } + test_case( "iterate over set", ct == 2 ); + + # Test adding elements to each set (Note: cannot add elements to sets + # of multiple types) + + add s1["added"]; + add s1["added"]; # element already exists (nothing happens) + test_case( "add element", |s1| == 3 ); + test_case( "in operator", "added" in s1 ); + + add s2["another"]; + test_case( "add element", |s2| == 1 ); + add s2["test"]; + test_case( "add element", |s2| == 2 ); + test_case( "in operator", "another" in s2 ); + test_case( "in operator", "test" in s2 ); + + add s3["foo"]; + test_case( "add element", |s3| == 1 ); + test_case( "in operator", "foo" in s3 ); + + add s4["local"]; + test_case( "add element", |s4| == 2 ); + test_case( "in operator", "local" in s4 ); + + add sg1["global"]; + test_case( "add element", |sg1| == 3 ); + test_case( "in operator", "global" in sg1 ); + + add sg3["more global"]; + test_case( "add element", |sg3| == 4 ); + test_case( "in operator", "more global" in sg3 ); + + # Test removing elements from each set (Note: cannot remove elements + # from sets of multiple types) + + delete s1["test"]; + delete s1["foobar"]; # element does not exist (nothing happens) + test_case( "remove element", |s1| == 2 ); + test_case( "!in operator", "test" !in s1 ); + + delete s2["test"]; + test_case( "remove element", |s2| == 1 ); + test_case( "!in operator", "test" !in s2 ); + + delete s3["foo"]; + test_case( "remove element", |s3| == 0 ); + test_case( "!in operator", "foo" !in s3 ); + + delete s4["type inference"]; + test_case( "remove element", |s4| == 1 ); + test_case( "!in operator", "type inference" !in s4 ); + + delete sg1["braces"]; + test_case( "remove element", |sg1| == 2 ); + test_case( "!in operator", "braces" !in sg1 ); + + delete sg3["curly"]; + test_case( "remove element", |sg3| == 3 ); + test_case( "!in operator", "curly" !in sg3 ); + + + local a = set(1,5,7,9,8,14); + local b = set(1,7,9,2); + + local a_plus_b = set(1,2,5,7,9,8,14); + local a_also_b = set(1,7,9); + local a_sans_b = set(5,8,14); + local b_sans_a = set(2); + + local a_or_b = a | b; + local a_and_b = a & b; + + test_case( "union", a_or_b == a_plus_b ); + test_case( "intersection", a_and_b == a_plus_b ); + test_case( "difference", a - b == a_sans_b ); + test_case( "difference", b - a == b_sans_a ); + + test_case( "union/inter.", |b & set(1,7,9,2)| == |b | set(1,7,2,9)| ); + test_case( "relational", |b & a_or_b| == |b| && |b| < |a_or_b| ); + test_case( "relational", b < a_or_b && a < a_or_b && a_or_b > a_and_b ); + + test_case( "subset", b < a ); + test_case( "subset", a < b ); + test_case( "subset", b < (a | set(2)) ); + test_case( "superset", b > a ); + test_case( "superset", b > (a | set(2)) ); + test_case( "superset", b | set(8, 14, 5) > (a | set(2)) ); + test_case( "superset", b | set(8, 14, 99, 5) > (a | set(2)) ); + + test_case( "non-ordering", (a <= b) || (a >= b) ); + test_case( "non-ordering", (a <= a_or_b) && (a_or_b >= b) ); + + test_case( "superset", (b | set(14, 5)) > a - set(8) ); + test_case( "superset", (b | set(14)) > a - set(8) ); + test_case( "superset", (b | set(14)) > a - set(8,5) ); + test_case( "superset", b >= a - set(5,8,14) ); + test_case( "superset", b > a - set(5,8,14) ); + test_case( "superset", (b - set(2)) > a - set(5,8,14) ); + test_case( "equality", a == a | set(5) ); + test_case( "equality", a == a | set(5,11) ); + test_case( "non-equality", a != a | set(5,11) ); + test_case( "equality", a == a | set(5,11) ); + + test_case( "magnitude", |a_and_b| == |a_or_b|); + } + diff --git a/testing/btest/language/set.bro b/testing/btest/language/set.bro index d1eef7e6f0..56cd649b49 100644 --- a/testing/btest/language/set.bro +++ b/testing/btest/language/set.bro @@ -136,5 +136,50 @@ event bro_init() delete sg3["curly"]; test_case( "remove element", |sg3| == 3 ); test_case( "!in operator", "curly" !in sg3 ); + + + local a = set(1,5,7,9,8,14); + local b = set(1,7,9,2); + + local a_plus_b = set(1,2,5,7,9,8,14); + local a_also_b = set(1,7,9); + local a_sans_b = set(5,8,14); + local b_sans_a = set(2); + + local a_or_b = a | b; + local a_and_b = a & b; + + test_case( "union", a_or_b == a_plus_b ); + test_case( "intersection", a_and_b == a_plus_b ); + test_case( "difference", a - b == a_sans_b ); + test_case( "difference", b - a == b_sans_a ); + + test_case( "union/inter.", |b & set(1,7,9,2)| == |b | set(1,7,2,9)| ); + test_case( "relational", |b & a_or_b| == |b| && |b| < |a_or_b| ); + test_case( "relational", b < a_or_b && a < a_or_b && a_or_b > a_and_b ); + + test_case( "subset", b < a ); + test_case( "subset", a < b ); + test_case( "subset", b < (a | set(2)) ); + test_case( "superset", b > a ); + test_case( "superset", b > (a | set(2)) ); + test_case( "superset", b | set(8, 14, 5) > (a | set(2)) ); + test_case( "superset", b | set(8, 14, 99, 5) > (a | set(2)) ); + + test_case( "non-ordering", (a <= b) || (a >= b) ); + test_case( "non-ordering", (a <= a_or_b) && (a_or_b >= b) ); + + test_case( "superset", (b | set(14, 5)) > a - set(8) ); + test_case( "superset", (b | set(14)) > a - set(8) ); + test_case( "superset", (b | set(14)) > a - set(8,5) ); + test_case( "superset", b >= a - set(5,8,14) ); + test_case( "superset", b > a - set(5,8,14) ); + test_case( "superset", (b - set(2)) > a - set(5,8,14) ); + test_case( "equality", a == a | set(5) ); + test_case( "equality", a == a | set(5,11) ); + test_case( "non-equality", a != a | set(5,11) ); + test_case( "equality", a == a | set(5,11) ); + + test_case( "magnitude", |a_and_b| == |a_or_b|); }