charm-crypto-framework 0.61.1__cp313-cp313-macosx_10_13_universal2.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- charm/__init__.py +5 -0
- charm/adapters/__init__.py +0 -0
- charm/adapters/abenc_adapt_hybrid.py +90 -0
- charm/adapters/dabenc_adapt_hybrid.py +145 -0
- charm/adapters/ibenc_adapt_hybrid.py +72 -0
- charm/adapters/ibenc_adapt_identityhash.py +80 -0
- charm/adapters/kpabenc_adapt_hybrid.py +91 -0
- charm/adapters/pkenc_adapt_bchk05.py +121 -0
- charm/adapters/pkenc_adapt_chk04.py +91 -0
- charm/adapters/pkenc_adapt_hybrid.py +98 -0
- charm/adapters/pksig_adapt_naor01.py +89 -0
- charm/config.py +7 -0
- charm/core/__init__.py +0 -0
- charm/core/benchmark/benchmark_util.c +353 -0
- charm/core/benchmark/benchmark_util.h +61 -0
- charm/core/benchmark/benchmarkmodule.c +476 -0
- charm/core/benchmark/benchmarkmodule.h +162 -0
- charm/core/benchmark.cpython-313-darwin.so +0 -0
- charm/core/crypto/AES/AES.c +1464 -0
- charm/core/crypto/AES.cpython-313-darwin.so +0 -0
- charm/core/crypto/DES/DES.c +113 -0
- charm/core/crypto/DES.cpython-313-darwin.so +0 -0
- charm/core/crypto/DES3/DES3.c +26 -0
- charm/core/crypto/DES3.cpython-313-darwin.so +0 -0
- charm/core/crypto/__init__.py +0 -0
- charm/core/crypto/cryptobase/XOR.c +80 -0
- charm/core/crypto/cryptobase/_counter.c +496 -0
- charm/core/crypto/cryptobase/_counter.h +54 -0
- charm/core/crypto/cryptobase/block_template.c +900 -0
- charm/core/crypto/cryptobase/block_template.h +69 -0
- charm/core/crypto/cryptobase/cryptobasemodule.c +220 -0
- charm/core/crypto/cryptobase/libtom/tomcrypt.h +90 -0
- charm/core/crypto/cryptobase/libtom/tomcrypt_argchk.h +44 -0
- charm/core/crypto/cryptobase/libtom/tomcrypt_cfg.h +186 -0
- charm/core/crypto/cryptobase/libtom/tomcrypt_cipher.h +941 -0
- charm/core/crypto/cryptobase/libtom/tomcrypt_custom.h +556 -0
- charm/core/crypto/cryptobase/libtom/tomcrypt_des.c +1912 -0
- charm/core/crypto/cryptobase/libtom/tomcrypt_hash.h +407 -0
- charm/core/crypto/cryptobase/libtom/tomcrypt_mac.h +496 -0
- charm/core/crypto/cryptobase/libtom/tomcrypt_macros.h +435 -0
- charm/core/crypto/cryptobase/libtom/tomcrypt_math.h +534 -0
- charm/core/crypto/cryptobase/libtom/tomcrypt_misc.h +103 -0
- charm/core/crypto/cryptobase/libtom/tomcrypt_pk.h +653 -0
- charm/core/crypto/cryptobase/libtom/tomcrypt_pkcs.h +90 -0
- charm/core/crypto/cryptobase/libtom/tomcrypt_prng.h +199 -0
- charm/core/crypto/cryptobase/stream_template.c +271 -0
- charm/core/crypto/cryptobase/strxor.c +229 -0
- charm/core/crypto/cryptobase.cpython-313-darwin.so +0 -0
- charm/core/engine/__init__.py +5 -0
- charm/core/engine/protocol.py +293 -0
- charm/core/engine/util.py +174 -0
- charm/core/math/__init__.py +0 -0
- charm/core/math/elliptic_curve/ecmodule.c +1986 -0
- charm/core/math/elliptic_curve/ecmodule.h +230 -0
- charm/core/math/elliptic_curve.cpython-313-darwin.so +0 -0
- charm/core/math/elliptic_curve.pyi +63 -0
- charm/core/math/integer/integermodule.c +2539 -0
- charm/core/math/integer/integermodule.h +145 -0
- charm/core/math/integer.cpython-313-darwin.so +0 -0
- charm/core/math/integer.pyi +76 -0
- charm/core/math/pairing/miracl/miracl_config.h +37 -0
- charm/core/math/pairing/miracl/miracl_interface.h +118 -0
- charm/core/math/pairing/miracl/miracl_interface2.h +126 -0
- charm/core/math/pairing/miracl/pairingmodule2.c +2094 -0
- charm/core/math/pairing/miracl/pairingmodule2.h +307 -0
- charm/core/math/pairing/pairingmodule.c +2230 -0
- charm/core/math/pairing/pairingmodule.h +241 -0
- charm/core/math/pairing/relic/pairingmodule3.c +1853 -0
- charm/core/math/pairing/relic/pairingmodule3.h +233 -0
- charm/core/math/pairing/relic/relic_interface.c +1337 -0
- charm/core/math/pairing/relic/relic_interface.h +217 -0
- charm/core/math/pairing/relic/test_relic.c +171 -0
- charm/core/math/pairing.cpython-313-darwin.so +0 -0
- charm/core/math/pairing.pyi +69 -0
- charm/core/utilities/base64.c +248 -0
- charm/core/utilities/base64.h +15 -0
- charm/schemes/__init__.py +0 -0
- charm/schemes/abenc/__init__.py +0 -0
- charm/schemes/abenc/abenc_accountability_jyjxgd20.py +647 -0
- charm/schemes/abenc/abenc_bsw07.py +146 -0
- charm/schemes/abenc/abenc_ca_cpabe_ar17.py +684 -0
- charm/schemes/abenc/abenc_dacmacs_yj14.py +298 -0
- charm/schemes/abenc/abenc_lsw08.py +159 -0
- charm/schemes/abenc/abenc_maabe_rw15.py +236 -0
- charm/schemes/abenc/abenc_maabe_yj14.py +297 -0
- charm/schemes/abenc/abenc_tbpre_lww14.py +309 -0
- charm/schemes/abenc/abenc_unmcpabe_yahk14.py +223 -0
- charm/schemes/abenc/abenc_waters09.py +144 -0
- charm/schemes/abenc/abenc_yct14.py +208 -0
- charm/schemes/abenc/abenc_yllc15.py +178 -0
- charm/schemes/abenc/ac17.py +248 -0
- charm/schemes/abenc/bsw07.py +141 -0
- charm/schemes/abenc/cgw15.py +277 -0
- charm/schemes/abenc/dabe_aw11.py +204 -0
- charm/schemes/abenc/dfa_fe12.py +144 -0
- charm/schemes/abenc/pk_hve08.py +179 -0
- charm/schemes/abenc/waters11.py +143 -0
- charm/schemes/aggrsign_MuSig.py +150 -0
- charm/schemes/aggrsign_bls.py +267 -0
- charm/schemes/blindsig_ps16.py +654 -0
- charm/schemes/chamhash_adm05.py +113 -0
- charm/schemes/chamhash_rsa_hw09.py +100 -0
- charm/schemes/commit/__init__.py +0 -0
- charm/schemes/commit/commit_gs08.py +77 -0
- charm/schemes/commit/commit_pedersen92.py +53 -0
- charm/schemes/encap_bchk05.py +62 -0
- charm/schemes/grpsig/__init__.py +0 -0
- charm/schemes/grpsig/groupsig_bgls04.py +114 -0
- charm/schemes/grpsig/groupsig_bgls04_var.py +115 -0
- charm/schemes/hibenc/__init__.py +0 -0
- charm/schemes/hibenc/hibenc_bb04.py +105 -0
- charm/schemes/hibenc/hibenc_lew11.py +193 -0
- charm/schemes/ibenc/__init__.py +0 -0
- charm/schemes/ibenc/clpkc_rp03.py +119 -0
- charm/schemes/ibenc/ibenc_CW13_z.py +168 -0
- charm/schemes/ibenc/ibenc_bb03.py +94 -0
- charm/schemes/ibenc/ibenc_bf01.py +121 -0
- charm/schemes/ibenc/ibenc_ckrs09.py +120 -0
- charm/schemes/ibenc/ibenc_cllww12_z.py +172 -0
- charm/schemes/ibenc/ibenc_lsw08.py +120 -0
- charm/schemes/ibenc/ibenc_sw05.py +238 -0
- charm/schemes/ibenc/ibenc_waters05.py +144 -0
- charm/schemes/ibenc/ibenc_waters05_z.py +164 -0
- charm/schemes/ibenc/ibenc_waters09.py +107 -0
- charm/schemes/ibenc/ibenc_waters09_z.py +147 -0
- charm/schemes/joye_scheme.py +106 -0
- charm/schemes/lem_scheme.py +207 -0
- charm/schemes/pk_fre_ccv11.py +107 -0
- charm/schemes/pk_vrf.py +127 -0
- charm/schemes/pkenc/__init__.py +0 -0
- charm/schemes/pkenc/pkenc_cs98.py +108 -0
- charm/schemes/pkenc/pkenc_elgamal85.py +122 -0
- charm/schemes/pkenc/pkenc_gm82.py +98 -0
- charm/schemes/pkenc/pkenc_paillier99.py +118 -0
- charm/schemes/pkenc/pkenc_rabin.py +254 -0
- charm/schemes/pkenc/pkenc_rsa.py +186 -0
- charm/schemes/pksig/__init__.py +0 -0
- charm/schemes/pksig/pksig_CW13_z.py +135 -0
- charm/schemes/pksig/pksig_bls04.py +87 -0
- charm/schemes/pksig/pksig_boyen.py +156 -0
- charm/schemes/pksig/pksig_chch.py +97 -0
- charm/schemes/pksig/pksig_chp.py +70 -0
- charm/schemes/pksig/pksig_cl03.py +150 -0
- charm/schemes/pksig/pksig_cl04.py +87 -0
- charm/schemes/pksig/pksig_cllww12_z.py +142 -0
- charm/schemes/pksig/pksig_cyh.py +132 -0
- charm/schemes/pksig/pksig_dsa.py +76 -0
- charm/schemes/pksig/pksig_ecdsa.py +71 -0
- charm/schemes/pksig/pksig_hess.py +104 -0
- charm/schemes/pksig/pksig_hw.py +110 -0
- charm/schemes/pksig/pksig_lamport.py +63 -0
- charm/schemes/pksig/pksig_ps01.py +135 -0
- charm/schemes/pksig/pksig_ps02.py +124 -0
- charm/schemes/pksig/pksig_ps03.py +119 -0
- charm/schemes/pksig/pksig_rsa_hw09.py +206 -0
- charm/schemes/pksig/pksig_schnorr91.py +77 -0
- charm/schemes/pksig/pksig_waters.py +115 -0
- charm/schemes/pksig/pksig_waters05.py +121 -0
- charm/schemes/pksig/pksig_waters09.py +121 -0
- charm/schemes/pre_mg07.py +150 -0
- charm/schemes/prenc/pre_afgh06.py +126 -0
- charm/schemes/prenc/pre_bbs98.py +123 -0
- charm/schemes/prenc/pre_nal16.py +216 -0
- charm/schemes/protocol_a01.py +272 -0
- charm/schemes/protocol_ao00.py +215 -0
- charm/schemes/protocol_cns07.py +274 -0
- charm/schemes/protocol_schnorr91.py +125 -0
- charm/schemes/sigma1.py +64 -0
- charm/schemes/sigma2.py +129 -0
- charm/schemes/sigma3.py +126 -0
- charm/schemes/threshold/__init__.py +59 -0
- charm/schemes/threshold/dkls23_dkg.py +556 -0
- charm/schemes/threshold/dkls23_presign.py +1089 -0
- charm/schemes/threshold/dkls23_sign.py +761 -0
- charm/schemes/threshold/xrpl_wallet.py +967 -0
- charm/test/__init__.py +0 -0
- charm/test/adapters/__init__.py +0 -0
- charm/test/adapters/abenc_adapt_hybrid_test.py +29 -0
- charm/test/adapters/dabenc_adapt_hybrid_test.py +56 -0
- charm/test/adapters/ibenc_adapt_hybrid_test.py +36 -0
- charm/test/adapters/ibenc_adapt_identityhash_test.py +32 -0
- charm/test/adapters/kpabenc_adapt_hybrid_test.py +30 -0
- charm/test/benchmark/abenc_yllc15_bench.py +92 -0
- charm/test/benchmark/benchmark_test.py +148 -0
- charm/test/benchmark_threshold.py +260 -0
- charm/test/conftest.py +38 -0
- charm/test/fuzz/__init__.py +1 -0
- charm/test/fuzz/conftest.py +5 -0
- charm/test/fuzz/fuzz_policy_parser.py +76 -0
- charm/test/fuzz/fuzz_serialization.py +83 -0
- charm/test/schemes/__init__.py +0 -0
- charm/test/schemes/abenc/__init__.py +0 -0
- charm/test/schemes/abenc/abenc_bsw07_test.py +39 -0
- charm/test/schemes/abenc/abenc_dacmacs_yj14_test.py +16 -0
- charm/test/schemes/abenc/abenc_lsw08_test.py +33 -0
- charm/test/schemes/abenc/abenc_maabe_yj14_test.py +16 -0
- charm/test/schemes/abenc/abenc_tbpre_lww14_test.py +16 -0
- charm/test/schemes/abenc/abenc_waters09_test.py +38 -0
- charm/test/schemes/abenc/abenc_yllc15_test.py +74 -0
- charm/test/schemes/chamhash_adm05_test.py +31 -0
- charm/test/schemes/chamhash_rsa_hw09_test.py +29 -0
- charm/test/schemes/commit/__init__.py +0 -0
- charm/test/schemes/commit/commit_gs08_test.py +24 -0
- charm/test/schemes/commit/commit_pedersen92_test.py +26 -0
- charm/test/schemes/dabe_aw11_test.py +45 -0
- charm/test/schemes/encap_bchk05_test.py +21 -0
- charm/test/schemes/grpsig/__init__.py +0 -0
- charm/test/schemes/grpsig/groupsig_bgls04_test.py +35 -0
- charm/test/schemes/grpsig/groupsig_bgls04_var_test.py +39 -0
- charm/test/schemes/hibenc/__init__.py +0 -0
- charm/test/schemes/hibenc/hibenc_bb04_test.py +28 -0
- charm/test/schemes/ibenc/__init__.py +0 -0
- charm/test/schemes/ibenc/ibenc_bb03_test.py +26 -0
- charm/test/schemes/ibenc/ibenc_bf01_test.py +24 -0
- charm/test/schemes/ibenc/ibenc_ckrs09_test.py +25 -0
- charm/test/schemes/ibenc/ibenc_lsw08_test.py +31 -0
- charm/test/schemes/ibenc/ibenc_sw05_test.py +32 -0
- charm/test/schemes/ibenc/ibenc_waters05_test.py +31 -0
- charm/test/schemes/ibenc/ibenc_waters09_test.py +27 -0
- charm/test/schemes/pk_vrf_test.py +29 -0
- charm/test/schemes/pkenc/__init__.py +0 -0
- charm/test/schemes/pkenc_test.py +255 -0
- charm/test/schemes/pksig/__init__.py +0 -0
- charm/test/schemes/pksig_test.py +376 -0
- charm/test/schemes/rsa_alg_test.py +340 -0
- charm/test/schemes/threshold_test.py +1792 -0
- charm/test/serialize/__init__.py +0 -0
- charm/test/serialize/serialize_test.py +40 -0
- charm/test/toolbox/__init__.py +0 -0
- charm/test/toolbox/conversion_test.py +30 -0
- charm/test/toolbox/ecgroup_test.py +53 -0
- charm/test/toolbox/integer_arithmetic_test.py +441 -0
- charm/test/toolbox/paddingschemes_test.py +238 -0
- charm/test/toolbox/policy_parser_stress_test.py +969 -0
- charm/test/toolbox/secretshare_test.py +28 -0
- charm/test/toolbox/symcrypto_test.py +108 -0
- charm/test/toolbox/test_policy_expression.py +16 -0
- charm/test/vectors/__init__.py +1 -0
- charm/test/vectors/test_bls_vectors.py +289 -0
- charm/test/vectors/test_pedersen_vectors.py +315 -0
- charm/test/vectors/test_schnorr_vectors.py +368 -0
- charm/test/zkp_compiler/__init__.py +9 -0
- charm/test/zkp_compiler/benchmark_zkp.py +258 -0
- charm/test/zkp_compiler/test_and_proof.py +240 -0
- charm/test/zkp_compiler/test_batch_verify.py +248 -0
- charm/test/zkp_compiler/test_dleq_proof.py +264 -0
- charm/test/zkp_compiler/test_or_proof.py +231 -0
- charm/test/zkp_compiler/test_proof_serialization.py +121 -0
- charm/test/zkp_compiler/test_range_proof.py +241 -0
- charm/test/zkp_compiler/test_representation_proof.py +325 -0
- charm/test/zkp_compiler/test_schnorr_proof.py +221 -0
- charm/test/zkp_compiler/test_thread_safety.py +169 -0
- charm/test/zkp_compiler/test_zkp_parser.py +139 -0
- charm/toolbox/ABEnc.py +26 -0
- charm/toolbox/ABEncMultiAuth.py +66 -0
- charm/toolbox/ABEnumeric.py +800 -0
- charm/toolbox/Commit.py +24 -0
- charm/toolbox/DFA.py +89 -0
- charm/toolbox/FSA.py +1254 -0
- charm/toolbox/Hash.py +39 -0
- charm/toolbox/IBEnc.py +62 -0
- charm/toolbox/IBSig.py +64 -0
- charm/toolbox/PKEnc.py +66 -0
- charm/toolbox/PKSig.py +56 -0
- charm/toolbox/PREnc.py +32 -0
- charm/toolbox/ZKProof.py +289 -0
- charm/toolbox/__init__.py +0 -0
- charm/toolbox/bitstring.py +49 -0
- charm/toolbox/broadcast.py +220 -0
- charm/toolbox/conversion.py +100 -0
- charm/toolbox/eccurve.py +149 -0
- charm/toolbox/ecgroup.py +143 -0
- charm/toolbox/enum.py +60 -0
- charm/toolbox/hash_module.py +91 -0
- charm/toolbox/integergroup.py +323 -0
- charm/toolbox/iterate.py +22 -0
- charm/toolbox/matrixops.py +76 -0
- charm/toolbox/mpc_utils.py +296 -0
- charm/toolbox/msp.py +175 -0
- charm/toolbox/mta.py +985 -0
- charm/toolbox/node.py +120 -0
- charm/toolbox/ot/__init__.py +22 -0
- charm/toolbox/ot/base_ot.py +374 -0
- charm/toolbox/ot/dpf.py +642 -0
- charm/toolbox/ot/mpfss.py +228 -0
- charm/toolbox/ot/ot_extension.py +589 -0
- charm/toolbox/ot/silent_ot.py +378 -0
- charm/toolbox/paddingschemes.py +423 -0
- charm/toolbox/paddingschemes_test.py +238 -0
- charm/toolbox/pairingcurves.py +85 -0
- charm/toolbox/pairinggroup.py +186 -0
- charm/toolbox/policy_expression_spec.py +70 -0
- charm/toolbox/policytree.py +189 -0
- charm/toolbox/reCompiler.py +346 -0
- charm/toolbox/redundancyschemes.py +65 -0
- charm/toolbox/schemebase.py +188 -0
- charm/toolbox/secretshare.py +104 -0
- charm/toolbox/secretutil.py +174 -0
- charm/toolbox/securerandom.py +73 -0
- charm/toolbox/sigmaprotocol.py +46 -0
- charm/toolbox/specialprimes.py +45 -0
- charm/toolbox/symcrypto.py +279 -0
- charm/toolbox/threshold_sharing.py +553 -0
- charm/toolbox/xmlserialize.py +94 -0
- charm/toolbox/zknode.py +105 -0
- charm/zkp_compiler/__init__.py +89 -0
- charm/zkp_compiler/and_proof.py +460 -0
- charm/zkp_compiler/batch_verify.py +324 -0
- charm/zkp_compiler/dleq_proof.py +423 -0
- charm/zkp_compiler/or_proof.py +305 -0
- charm/zkp_compiler/range_proof.py +417 -0
- charm/zkp_compiler/representation_proof.py +466 -0
- charm/zkp_compiler/schnorr_proof.py +273 -0
- charm/zkp_compiler/thread_safe.py +150 -0
- charm/zkp_compiler/zk_demo.py +489 -0
- charm/zkp_compiler/zkp_factory.py +330 -0
- charm/zkp_compiler/zkp_generator.py +370 -0
- charm/zkp_compiler/zkparser.py +269 -0
- charm_crypto_framework-0.61.1.dist-info/METADATA +337 -0
- charm_crypto_framework-0.61.1.dist-info/RECORD +323 -0
- charm_crypto_framework-0.61.1.dist-info/WHEEL +5 -0
- charm_crypto_framework-0.61.1.dist-info/licenses/LICENSE.txt +165 -0
- charm_crypto_framework-0.61.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,969 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""
|
|
3
|
+
Comprehensive stress test for the ABE policy parser.
|
|
4
|
+
|
|
5
|
+
This script tests the PolicyParser and MSP classes for expressiveness,
|
|
6
|
+
correctness, and robustness. It can be run independently to verify
|
|
7
|
+
the policy parser functionality.
|
|
8
|
+
|
|
9
|
+
Usage:
|
|
10
|
+
python -m charm.test.toolbox.policy_parser_stress_test
|
|
11
|
+
|
|
12
|
+
# Or with pytest:
|
|
13
|
+
pytest charm/test/toolbox/policy_parser_stress_test.py -v
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import sys
|
|
17
|
+
import time
|
|
18
|
+
import random
|
|
19
|
+
import string
|
|
20
|
+
import unittest
|
|
21
|
+
from typing import List, Tuple, Optional
|
|
22
|
+
|
|
23
|
+
from charm.toolbox.policytree import PolicyParser
|
|
24
|
+
from charm.toolbox.node import OpType, BinNode
|
|
25
|
+
from charm.toolbox.msp import MSP
|
|
26
|
+
from charm.toolbox.pairinggroup import PairingGroup
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class PolicyParserStressTest(unittest.TestCase):
|
|
30
|
+
"""Comprehensive stress tests for the ABE policy parser."""
|
|
31
|
+
|
|
32
|
+
@classmethod
|
|
33
|
+
def setUpClass(cls):
|
|
34
|
+
cls.parser = PolicyParser()
|
|
35
|
+
cls.group = PairingGroup('SS512')
|
|
36
|
+
cls.msp = MSP(cls.group)
|
|
37
|
+
|
|
38
|
+
# =========================================================================
|
|
39
|
+
# Basic Parsing Tests
|
|
40
|
+
# =========================================================================
|
|
41
|
+
|
|
42
|
+
def test_single_attribute(self):
|
|
43
|
+
"""Test parsing single attributes."""
|
|
44
|
+
# Note: underscore followed by digits is treated as duplicate index
|
|
45
|
+
# e.g., ATTR_123 becomes ATTR with index 123
|
|
46
|
+
test_cases = [
|
|
47
|
+
('A', 'A'),
|
|
48
|
+
('attribute', 'ATTRIBUTE'),
|
|
49
|
+
('role-admin', 'ROLE-ADMIN'),
|
|
50
|
+
('user.name', 'USER.NAME'),
|
|
51
|
+
]
|
|
52
|
+
for attr, expected in test_cases:
|
|
53
|
+
tree = self.parser.parse(attr)
|
|
54
|
+
self.assertEqual(tree.getNodeType(), OpType.ATTR)
|
|
55
|
+
self.assertEqual(tree.getAttribute(), expected)
|
|
56
|
+
|
|
57
|
+
def test_attribute_with_index(self):
|
|
58
|
+
"""Test attributes with numeric index suffix (used for duplicates)."""
|
|
59
|
+
# ATTR_123 is parsed as attribute ATTR with index 123
|
|
60
|
+
tree = self.parser.parse('ATTR_123')
|
|
61
|
+
self.assertEqual(tree.getNodeType(), OpType.ATTR)
|
|
62
|
+
self.assertEqual(tree.getAttribute(), 'ATTR')
|
|
63
|
+
self.assertEqual(tree.index, 123)
|
|
64
|
+
|
|
65
|
+
def test_basic_and(self):
|
|
66
|
+
"""Test basic AND operations."""
|
|
67
|
+
for op in ['and', 'AND']:
|
|
68
|
+
tree = self.parser.parse(f'A {op} B')
|
|
69
|
+
self.assertEqual(tree.getNodeType(), OpType.AND)
|
|
70
|
+
|
|
71
|
+
def test_basic_or(self):
|
|
72
|
+
"""Test basic OR operations."""
|
|
73
|
+
for op in ['or', 'OR']:
|
|
74
|
+
tree = self.parser.parse(f'A {op} B')
|
|
75
|
+
self.assertEqual(tree.getNodeType(), OpType.OR)
|
|
76
|
+
|
|
77
|
+
def test_nested_expressions(self):
|
|
78
|
+
"""Test nested policy expressions."""
|
|
79
|
+
test_cases = [
|
|
80
|
+
('(A and B) or C', OpType.OR),
|
|
81
|
+
('A and (B or C)', OpType.AND),
|
|
82
|
+
('((A and B) or C) and D', OpType.AND),
|
|
83
|
+
('(A or B) and (C or D)', OpType.AND),
|
|
84
|
+
]
|
|
85
|
+
for policy, expected_root_type in test_cases:
|
|
86
|
+
tree = self.parser.parse(policy)
|
|
87
|
+
self.assertEqual(tree.getNodeType(), expected_root_type,
|
|
88
|
+
f"Failed for policy: {policy}")
|
|
89
|
+
|
|
90
|
+
def test_negated_attributes(self):
|
|
91
|
+
"""Test negated attribute parsing."""
|
|
92
|
+
tree = self.parser.parse('!A and B')
|
|
93
|
+
left = tree.getLeft()
|
|
94
|
+
self.assertTrue(left.negated)
|
|
95
|
+
self.assertEqual(left.getAttribute(), '!A')
|
|
96
|
+
|
|
97
|
+
# =========================================================================
|
|
98
|
+
# Stress Tests
|
|
99
|
+
# =========================================================================
|
|
100
|
+
|
|
101
|
+
def test_deep_nesting(self):
|
|
102
|
+
"""Test deeply nested expressions (20 levels)."""
|
|
103
|
+
policy = 'A'
|
|
104
|
+
for i in range(20):
|
|
105
|
+
policy = f'({policy} and B{i})'
|
|
106
|
+
tree = self.parser.parse(policy)
|
|
107
|
+
self.assertIsNotNone(tree)
|
|
108
|
+
|
|
109
|
+
def test_many_attributes(self):
|
|
110
|
+
"""Test policy with 100 attributes."""
|
|
111
|
+
attrs = ' and '.join([f'ATTR{i}' for i in range(100)])
|
|
112
|
+
tree = self.parser.parse(attrs)
|
|
113
|
+
self.assertIsNotNone(tree)
|
|
114
|
+
|
|
115
|
+
# Verify all attributes are present
|
|
116
|
+
attr_list = self.msp.getAttributeList(tree)
|
|
117
|
+
self.assertEqual(len(attr_list), 100)
|
|
118
|
+
|
|
119
|
+
def test_wide_or_tree(self):
|
|
120
|
+
"""Test wide OR tree with 50 branches."""
|
|
121
|
+
attrs = ' or '.join([f'ATTR{i}' for i in range(50)])
|
|
122
|
+
tree = self.parser.parse(attrs)
|
|
123
|
+
self.assertIsNotNone(tree)
|
|
124
|
+
|
|
125
|
+
def test_balanced_tree(self):
|
|
126
|
+
"""Test balanced binary tree structure."""
|
|
127
|
+
# Create: ((A and B) or (C and D)) and ((E and F) or (G and H))
|
|
128
|
+
policy = '((A and B) or (C and D)) and ((E and F) or (G and H))'
|
|
129
|
+
tree = self.parser.parse(policy)
|
|
130
|
+
self.assertEqual(tree.getNodeType(), OpType.AND)
|
|
131
|
+
|
|
132
|
+
def test_random_policies(self):
|
|
133
|
+
"""Generate and parse 100 random valid policies."""
|
|
134
|
+
for _ in range(100):
|
|
135
|
+
policy = self._generate_random_policy(depth=5, max_attrs=10)
|
|
136
|
+
try:
|
|
137
|
+
tree = self.parser.parse(policy)
|
|
138
|
+
self.assertIsNotNone(tree)
|
|
139
|
+
except Exception as e:
|
|
140
|
+
self.fail(f"Failed to parse random policy: {policy}\nError: {e}")
|
|
141
|
+
|
|
142
|
+
# =========================================================================
|
|
143
|
+
# Policy Satisfaction Tests
|
|
144
|
+
# =========================================================================
|
|
145
|
+
|
|
146
|
+
def test_prune_and_policy(self):
|
|
147
|
+
"""Test policy satisfaction for AND policies."""
|
|
148
|
+
tree = self.parser.parse('A and B and C')
|
|
149
|
+
|
|
150
|
+
# All attributes present - should satisfy
|
|
151
|
+
result = self.parser.prune(tree, ['A', 'B', 'C'])
|
|
152
|
+
self.assertIsNotNone(result)
|
|
153
|
+
self.assertNotEqual(result, False)
|
|
154
|
+
|
|
155
|
+
# Missing one attribute - should not satisfy
|
|
156
|
+
result = self.parser.prune(tree, ['A', 'B'])
|
|
157
|
+
self.assertFalse(result)
|
|
158
|
+
|
|
159
|
+
def test_prune_or_policy(self):
|
|
160
|
+
"""Test policy satisfaction for OR policies."""
|
|
161
|
+
tree = self.parser.parse('A or B or C')
|
|
162
|
+
|
|
163
|
+
# Any single attribute should satisfy
|
|
164
|
+
for attr in ['A', 'B', 'C']:
|
|
165
|
+
result = self.parser.prune(tree, [attr])
|
|
166
|
+
self.assertIsNotNone(result)
|
|
167
|
+
self.assertNotEqual(result, False)
|
|
168
|
+
|
|
169
|
+
# No attributes - should not satisfy
|
|
170
|
+
result = self.parser.prune(tree, [])
|
|
171
|
+
self.assertFalse(result)
|
|
172
|
+
|
|
173
|
+
def test_prune_complex_policy(self):
|
|
174
|
+
"""Test policy satisfaction for complex policies."""
|
|
175
|
+
tree = self.parser.parse('(A and B) or (C and D)')
|
|
176
|
+
|
|
177
|
+
# Left branch satisfied
|
|
178
|
+
result = self.parser.prune(tree, ['A', 'B'])
|
|
179
|
+
self.assertIsNotNone(result)
|
|
180
|
+
|
|
181
|
+
# Right branch satisfied
|
|
182
|
+
result = self.parser.prune(tree, ['C', 'D'])
|
|
183
|
+
self.assertIsNotNone(result)
|
|
184
|
+
|
|
185
|
+
# Neither branch satisfied
|
|
186
|
+
result = self.parser.prune(tree, ['A', 'C'])
|
|
187
|
+
self.assertFalse(result)
|
|
188
|
+
|
|
189
|
+
# =========================================================================
|
|
190
|
+
# MSP Conversion Tests
|
|
191
|
+
# =========================================================================
|
|
192
|
+
|
|
193
|
+
def test_msp_simple_and(self):
|
|
194
|
+
"""Test MSP conversion for AND policy."""
|
|
195
|
+
tree = self.msp.createPolicy('A and B')
|
|
196
|
+
matrix = self.msp.convert_policy_to_msp(tree)
|
|
197
|
+
|
|
198
|
+
self.assertIn('A', matrix)
|
|
199
|
+
self.assertIn('B', matrix)
|
|
200
|
+
# AND gate: first child gets [1, 1], second gets [0, -1]
|
|
201
|
+
self.assertEqual(matrix['A'], [1, 1])
|
|
202
|
+
self.assertEqual(matrix['B'], [0, -1])
|
|
203
|
+
|
|
204
|
+
def test_msp_simple_or(self):
|
|
205
|
+
"""Test MSP conversion for OR policy."""
|
|
206
|
+
tree = self.msp.createPolicy('A or B')
|
|
207
|
+
matrix = self.msp.convert_policy_to_msp(tree)
|
|
208
|
+
|
|
209
|
+
self.assertIn('A', matrix)
|
|
210
|
+
self.assertIn('B', matrix)
|
|
211
|
+
# OR gate: both children get same vector
|
|
212
|
+
self.assertEqual(matrix['A'], [1])
|
|
213
|
+
self.assertEqual(matrix['B'], [1])
|
|
214
|
+
|
|
215
|
+
def test_msp_complex_policy(self):
|
|
216
|
+
"""Test MSP conversion for complex policy."""
|
|
217
|
+
policy = '((A and B) or C) and D'
|
|
218
|
+
tree = self.msp.createPolicy(policy)
|
|
219
|
+
matrix = self.msp.convert_policy_to_msp(tree)
|
|
220
|
+
|
|
221
|
+
# All attributes should be in the matrix
|
|
222
|
+
for attr in ['A', 'B', 'C', 'D']:
|
|
223
|
+
self.assertIn(attr, matrix)
|
|
224
|
+
|
|
225
|
+
def test_msp_coefficient_recovery(self):
|
|
226
|
+
"""Test coefficient recovery from MSP."""
|
|
227
|
+
tree = self.msp.createPolicy('A and B')
|
|
228
|
+
coeffs = self.msp.getCoefficients(tree)
|
|
229
|
+
|
|
230
|
+
self.assertIn('A', coeffs)
|
|
231
|
+
self.assertIn('B', coeffs)
|
|
232
|
+
|
|
233
|
+
# =========================================================================
|
|
234
|
+
# Duplicate Attribute Tests
|
|
235
|
+
# =========================================================================
|
|
236
|
+
|
|
237
|
+
def test_duplicate_attributes(self):
|
|
238
|
+
"""Test handling of duplicate attributes."""
|
|
239
|
+
tree = self.parser.parse('A and B and A')
|
|
240
|
+
|
|
241
|
+
_dictCount = {}
|
|
242
|
+
self.parser.findDuplicates(tree, _dictCount)
|
|
243
|
+
|
|
244
|
+
self.assertEqual(_dictCount['A'], 2)
|
|
245
|
+
self.assertEqual(_dictCount['B'], 1)
|
|
246
|
+
|
|
247
|
+
def test_duplicate_labeling(self):
|
|
248
|
+
"""Test that duplicate attributes get unique labels."""
|
|
249
|
+
tree = self.msp.createPolicy('A and B and A')
|
|
250
|
+
attr_list = self.msp.getAttributeList(tree)
|
|
251
|
+
|
|
252
|
+
# Should have 3 attributes with unique labels
|
|
253
|
+
self.assertEqual(len(attr_list), 3)
|
|
254
|
+
# Check that duplicates are labeled (A_0, A_1)
|
|
255
|
+
a_attrs = [a for a in attr_list if a.startswith('A')]
|
|
256
|
+
self.assertEqual(len(a_attrs), 2)
|
|
257
|
+
|
|
258
|
+
# =========================================================================
|
|
259
|
+
# Special Character Tests
|
|
260
|
+
# =========================================================================
|
|
261
|
+
|
|
262
|
+
def test_special_characters_in_attributes(self):
|
|
263
|
+
"""Test attributes with special characters."""
|
|
264
|
+
# Note: underscore followed by non-digits works, but underscore + digits
|
|
265
|
+
# is treated as duplicate index notation (e.g., ATTR_0, ATTR_1)
|
|
266
|
+
special_attrs = [
|
|
267
|
+
'attr-name', # hyphen
|
|
268
|
+
'attr.name', # dot
|
|
269
|
+
'attr@domain', # at sign
|
|
270
|
+
'attr#123', # hash
|
|
271
|
+
'attr$var', # dollar
|
|
272
|
+
'role/admin', # slash
|
|
273
|
+
]
|
|
274
|
+
for attr in special_attrs:
|
|
275
|
+
try:
|
|
276
|
+
tree = self.parser.parse(attr)
|
|
277
|
+
self.assertEqual(tree.getNodeType(), OpType.ATTR)
|
|
278
|
+
except Exception as e:
|
|
279
|
+
self.fail(f"Failed to parse attribute: {attr}\nError: {e}")
|
|
280
|
+
|
|
281
|
+
def test_underscore_limitation(self):
|
|
282
|
+
"""Test that underscore + non-digits fails (known limitation)."""
|
|
283
|
+
# This is a known limitation: attr_name fails because the parser
|
|
284
|
+
# expects digits after underscore for duplicate indexing
|
|
285
|
+
with self.assertRaises(Exception):
|
|
286
|
+
self.parser.parse('attr_name')
|
|
287
|
+
|
|
288
|
+
# =========================================================================
|
|
289
|
+
# Performance Tests
|
|
290
|
+
# =========================================================================
|
|
291
|
+
|
|
292
|
+
def test_parsing_performance(self):
|
|
293
|
+
"""Test parsing performance with 1000 iterations."""
|
|
294
|
+
policy = '(A and B) or (C and D) or (E and F)'
|
|
295
|
+
|
|
296
|
+
start = time.time()
|
|
297
|
+
for _ in range(1000):
|
|
298
|
+
self.parser.parse(policy)
|
|
299
|
+
elapsed = time.time() - start
|
|
300
|
+
|
|
301
|
+
# Should complete in under 5 seconds
|
|
302
|
+
self.assertLess(elapsed, 5.0,
|
|
303
|
+
f"Parsing 1000 policies took {elapsed:.2f}s (expected < 5s)")
|
|
304
|
+
|
|
305
|
+
def test_msp_conversion_performance(self):
|
|
306
|
+
"""Test MSP conversion performance."""
|
|
307
|
+
policy = ' and '.join([f'ATTR{i}' for i in range(20)])
|
|
308
|
+
tree = self.msp.createPolicy(policy)
|
|
309
|
+
|
|
310
|
+
start = time.time()
|
|
311
|
+
for _ in range(100):
|
|
312
|
+
self.msp.convert_policy_to_msp(tree)
|
|
313
|
+
elapsed = time.time() - start
|
|
314
|
+
|
|
315
|
+
# Should complete in under 2 seconds
|
|
316
|
+
self.assertLess(elapsed, 2.0,
|
|
317
|
+
f"MSP conversion took {elapsed:.2f}s (expected < 2s)")
|
|
318
|
+
|
|
319
|
+
# =========================================================================
|
|
320
|
+
# Helper Methods
|
|
321
|
+
# =========================================================================
|
|
322
|
+
|
|
323
|
+
def _generate_random_policy(self, depth: int, max_attrs: int) -> str:
|
|
324
|
+
"""Generate a random valid policy expression."""
|
|
325
|
+
if depth <= 0 or random.random() < 0.3:
|
|
326
|
+
# Generate leaf node (attribute)
|
|
327
|
+
return f'ATTR{random.randint(0, max_attrs)}'
|
|
328
|
+
|
|
329
|
+
# Generate internal node
|
|
330
|
+
left = self._generate_random_policy(depth - 1, max_attrs)
|
|
331
|
+
right = self._generate_random_policy(depth - 1, max_attrs)
|
|
332
|
+
op = random.choice(['and', 'or'])
|
|
333
|
+
return f'({left} {op} {right})'
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
class PolicyParserEdgeCaseTest(unittest.TestCase):
|
|
337
|
+
"""Edge case tests for the policy parser."""
|
|
338
|
+
|
|
339
|
+
@classmethod
|
|
340
|
+
def setUpClass(cls):
|
|
341
|
+
cls.parser = PolicyParser()
|
|
342
|
+
|
|
343
|
+
def test_case_insensitive_operators(self):
|
|
344
|
+
"""Test that AND/OR operators are case-insensitive."""
|
|
345
|
+
# These should all parse correctly
|
|
346
|
+
for policy in ['A AND B', 'A and B', 'A OR B', 'A or B']:
|
|
347
|
+
tree = self.parser.parse(policy)
|
|
348
|
+
self.assertIsNotNone(tree)
|
|
349
|
+
|
|
350
|
+
def test_whitespace_handling(self):
|
|
351
|
+
"""Test handling of extra whitespace."""
|
|
352
|
+
policies = [
|
|
353
|
+
'A and B', # extra spaces
|
|
354
|
+
' A and B ', # leading/trailing spaces
|
|
355
|
+
'A and B', # mixed spacing
|
|
356
|
+
]
|
|
357
|
+
for policy in policies:
|
|
358
|
+
tree = self.parser.parse(policy)
|
|
359
|
+
self.assertIsNotNone(tree)
|
|
360
|
+
|
|
361
|
+
def test_parentheses_variations(self):
|
|
362
|
+
"""Test various parentheses patterns."""
|
|
363
|
+
policies = [
|
|
364
|
+
'(A)',
|
|
365
|
+
'((A))',
|
|
366
|
+
'(A and B)',
|
|
367
|
+
'((A and B))',
|
|
368
|
+
'(A) and (B)',
|
|
369
|
+
]
|
|
370
|
+
for policy in policies:
|
|
371
|
+
tree = self.parser.parse(policy)
|
|
372
|
+
self.assertIsNotNone(tree)
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
class NumericAttributeTest(unittest.TestCase):
|
|
376
|
+
"""Tests for numeric attribute support using bag of bits encoding."""
|
|
377
|
+
|
|
378
|
+
@classmethod
|
|
379
|
+
def setUpClass(cls):
|
|
380
|
+
cls.parser = PolicyParser()
|
|
381
|
+
# Import here to avoid circular imports
|
|
382
|
+
from charm.toolbox.ABEnumeric import NumericAttributeHelper
|
|
383
|
+
cls.helper = NumericAttributeHelper(num_bits=8)
|
|
384
|
+
|
|
385
|
+
def test_greater_than(self):
|
|
386
|
+
"""Test attr > value comparison."""
|
|
387
|
+
expanded = self.helper.expand_policy('age > 10')
|
|
388
|
+
tree = self.parser.parse(expanded)
|
|
389
|
+
|
|
390
|
+
# age=15 should satisfy age > 10
|
|
391
|
+
user_attrs = self.helper.user_attributes({'age': 15})
|
|
392
|
+
result = self.parser.prune(tree, user_attrs)
|
|
393
|
+
self.assertTrue(result)
|
|
394
|
+
|
|
395
|
+
# age=10 should NOT satisfy age > 10
|
|
396
|
+
user_attrs = self.helper.user_attributes({'age': 10})
|
|
397
|
+
result = self.parser.prune(tree, user_attrs)
|
|
398
|
+
self.assertFalse(result)
|
|
399
|
+
|
|
400
|
+
# age=5 should NOT satisfy age > 10
|
|
401
|
+
user_attrs = self.helper.user_attributes({'age': 5})
|
|
402
|
+
result = self.parser.prune(tree, user_attrs)
|
|
403
|
+
self.assertFalse(result)
|
|
404
|
+
|
|
405
|
+
def test_greater_than_or_equal(self):
|
|
406
|
+
"""Test attr >= value comparison."""
|
|
407
|
+
expanded = self.helper.expand_policy('age >= 18')
|
|
408
|
+
tree = self.parser.parse(expanded)
|
|
409
|
+
|
|
410
|
+
# age=25 should satisfy
|
|
411
|
+
user_attrs = self.helper.user_attributes({'age': 25})
|
|
412
|
+
self.assertTrue(self.parser.prune(tree, user_attrs))
|
|
413
|
+
|
|
414
|
+
# age=18 should satisfy (boundary)
|
|
415
|
+
user_attrs = self.helper.user_attributes({'age': 18})
|
|
416
|
+
self.assertTrue(self.parser.prune(tree, user_attrs))
|
|
417
|
+
|
|
418
|
+
# age=17 should NOT satisfy
|
|
419
|
+
user_attrs = self.helper.user_attributes({'age': 17})
|
|
420
|
+
self.assertFalse(self.parser.prune(tree, user_attrs))
|
|
421
|
+
|
|
422
|
+
def test_less_than(self):
|
|
423
|
+
"""Test attr < value comparison."""
|
|
424
|
+
expanded = self.helper.expand_policy('age < 18')
|
|
425
|
+
tree = self.parser.parse(expanded)
|
|
426
|
+
|
|
427
|
+
# age=17 should satisfy
|
|
428
|
+
user_attrs = self.helper.user_attributes({'age': 17})
|
|
429
|
+
self.assertTrue(self.parser.prune(tree, user_attrs))
|
|
430
|
+
|
|
431
|
+
# age=18 should NOT satisfy
|
|
432
|
+
user_attrs = self.helper.user_attributes({'age': 18})
|
|
433
|
+
self.assertFalse(self.parser.prune(tree, user_attrs))
|
|
434
|
+
|
|
435
|
+
# age=25 should NOT satisfy
|
|
436
|
+
user_attrs = self.helper.user_attributes({'age': 25})
|
|
437
|
+
self.assertFalse(self.parser.prune(tree, user_attrs))
|
|
438
|
+
|
|
439
|
+
def test_less_than_or_equal(self):
|
|
440
|
+
"""Test attr <= value comparison."""
|
|
441
|
+
expanded = self.helper.expand_policy('level <= 5')
|
|
442
|
+
tree = self.parser.parse(expanded)
|
|
443
|
+
|
|
444
|
+
# level=3 should satisfy
|
|
445
|
+
user_attrs = self.helper.user_attributes({'level': 3})
|
|
446
|
+
self.assertTrue(self.parser.prune(tree, user_attrs))
|
|
447
|
+
|
|
448
|
+
# level=5 should satisfy (boundary)
|
|
449
|
+
user_attrs = self.helper.user_attributes({'level': 5})
|
|
450
|
+
self.assertTrue(self.parser.prune(tree, user_attrs))
|
|
451
|
+
|
|
452
|
+
# level=6 should NOT satisfy
|
|
453
|
+
user_attrs = self.helper.user_attributes({'level': 6})
|
|
454
|
+
self.assertFalse(self.parser.prune(tree, user_attrs))
|
|
455
|
+
|
|
456
|
+
def test_equality(self):
|
|
457
|
+
"""Test attr == value comparison."""
|
|
458
|
+
expanded = self.helper.expand_policy('level == 5')
|
|
459
|
+
tree = self.parser.parse(expanded)
|
|
460
|
+
|
|
461
|
+
# level=5 should satisfy
|
|
462
|
+
user_attrs = self.helper.user_attributes({'level': 5})
|
|
463
|
+
self.assertTrue(self.parser.prune(tree, user_attrs))
|
|
464
|
+
|
|
465
|
+
# level=4 should NOT satisfy
|
|
466
|
+
user_attrs = self.helper.user_attributes({'level': 4})
|
|
467
|
+
self.assertFalse(self.parser.prune(tree, user_attrs))
|
|
468
|
+
|
|
469
|
+
# level=6 should NOT satisfy
|
|
470
|
+
user_attrs = self.helper.user_attributes({'level': 6})
|
|
471
|
+
self.assertFalse(self.parser.prune(tree, user_attrs))
|
|
472
|
+
|
|
473
|
+
def test_compound_numeric_policy(self):
|
|
474
|
+
"""Test combined numeric comparisons."""
|
|
475
|
+
expanded = self.helper.expand_policy('age >= 18 and level > 5')
|
|
476
|
+
tree = self.parser.parse(expanded)
|
|
477
|
+
|
|
478
|
+
# age=25, level=10 should satisfy both
|
|
479
|
+
user_attrs = self.helper.user_attributes({'age': 25, 'level': 10})
|
|
480
|
+
self.assertTrue(self.parser.prune(tree, user_attrs))
|
|
481
|
+
|
|
482
|
+
# age=25, level=3 should fail (level > 5 fails)
|
|
483
|
+
user_attrs = self.helper.user_attributes({'age': 25, 'level': 3})
|
|
484
|
+
self.assertFalse(self.parser.prune(tree, user_attrs))
|
|
485
|
+
|
|
486
|
+
# age=15, level=10 should fail (age >= 18 fails)
|
|
487
|
+
user_attrs = self.helper.user_attributes({'age': 15, 'level': 10})
|
|
488
|
+
self.assertFalse(self.parser.prune(tree, user_attrs))
|
|
489
|
+
|
|
490
|
+
def test_mixed_numeric_and_string_policy(self):
|
|
491
|
+
"""Test policies mixing numeric comparisons and string attributes."""
|
|
492
|
+
expanded = self.helper.expand_policy('(age >= 21 or admin) and level > 0')
|
|
493
|
+
tree = self.parser.parse(expanded)
|
|
494
|
+
|
|
495
|
+
# age=25, level=1 should satisfy (age >= 21 satisfies first clause)
|
|
496
|
+
user_attrs = self.helper.user_attributes({'age': 25, 'level': 1})
|
|
497
|
+
self.assertTrue(self.parser.prune(tree, user_attrs))
|
|
498
|
+
|
|
499
|
+
# role=admin, level=1 should satisfy (admin satisfies first clause)
|
|
500
|
+
user_attrs = self.helper.user_attributes({'level': 1, 'role': 'ADMIN'})
|
|
501
|
+
result = self.parser.prune(tree, user_attrs)
|
|
502
|
+
self.assertTrue(result)
|
|
503
|
+
|
|
504
|
+
def test_bit_encoding_correctness(self):
|
|
505
|
+
"""Test that bit encoding is correct for various values."""
|
|
506
|
+
from charm.toolbox.ABEnumeric import int_to_bits
|
|
507
|
+
|
|
508
|
+
# Test specific values
|
|
509
|
+
self.assertEqual(int_to_bits(0, 8), [0, 0, 0, 0, 0, 0, 0, 0])
|
|
510
|
+
self.assertEqual(int_to_bits(1, 8), [1, 0, 0, 0, 0, 0, 0, 0])
|
|
511
|
+
self.assertEqual(int_to_bits(5, 8), [1, 0, 1, 0, 0, 0, 0, 0]) # 101
|
|
512
|
+
self.assertEqual(int_to_bits(255, 8), [1, 1, 1, 1, 1, 1, 1, 1])
|
|
513
|
+
|
|
514
|
+
def test_boundary_values(self):
|
|
515
|
+
"""Test boundary conditions for numeric comparisons."""
|
|
516
|
+
# Test at boundary of 8-bit range
|
|
517
|
+
expanded = self.helper.expand_policy('val >= 255')
|
|
518
|
+
tree = self.parser.parse(expanded)
|
|
519
|
+
|
|
520
|
+
# val=255 should satisfy (exactly equal)
|
|
521
|
+
user_attrs = self.helper.user_attributes({'val': 255})
|
|
522
|
+
self.assertTrue(self.parser.prune(tree, user_attrs))
|
|
523
|
+
|
|
524
|
+
# val=254 should NOT satisfy
|
|
525
|
+
user_attrs = self.helper.user_attributes({'val': 254})
|
|
526
|
+
self.assertFalse(self.parser.prune(tree, user_attrs))
|
|
527
|
+
|
|
528
|
+
def test_zero_comparisons(self):
|
|
529
|
+
"""Test comparisons with zero."""
|
|
530
|
+
# age > 0
|
|
531
|
+
expanded = self.helper.expand_policy('age > 0')
|
|
532
|
+
tree = self.parser.parse(expanded)
|
|
533
|
+
|
|
534
|
+
user_attrs = self.helper.user_attributes({'age': 1})
|
|
535
|
+
self.assertTrue(self.parser.prune(tree, user_attrs))
|
|
536
|
+
|
|
537
|
+
user_attrs = self.helper.user_attributes({'age': 0})
|
|
538
|
+
self.assertFalse(self.parser.prune(tree, user_attrs))
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
class NumericAttributeEdgeCaseTest(unittest.TestCase):
|
|
542
|
+
"""Tests for edge cases in numeric attribute handling."""
|
|
543
|
+
|
|
544
|
+
def setUp(self):
|
|
545
|
+
from charm.toolbox.ABEnumeric import (
|
|
546
|
+
NumericAttributeHelper, preprocess_numeric_policy,
|
|
547
|
+
expand_numeric_comparison, int_to_bits, validate_num_bits,
|
|
548
|
+
validate_attribute_name, BitOverflowError, InvalidBitWidthError,
|
|
549
|
+
InvalidOperatorError, AttributeNameConflictError
|
|
550
|
+
)
|
|
551
|
+
self.helper = NumericAttributeHelper(num_bits=8)
|
|
552
|
+
self.strict_helper = NumericAttributeHelper(num_bits=8, strict=True)
|
|
553
|
+
self.preprocess = preprocess_numeric_policy
|
|
554
|
+
self.expand = expand_numeric_comparison
|
|
555
|
+
self.int_to_bits = int_to_bits
|
|
556
|
+
self.validate_num_bits = validate_num_bits
|
|
557
|
+
self.validate_attribute_name = validate_attribute_name
|
|
558
|
+
self.BitOverflowError = BitOverflowError
|
|
559
|
+
self.InvalidBitWidthError = InvalidBitWidthError
|
|
560
|
+
self.InvalidOperatorError = InvalidOperatorError
|
|
561
|
+
self.AttributeNameConflictError = AttributeNameConflictError
|
|
562
|
+
|
|
563
|
+
# --- Bit Overflow Tests ---
|
|
564
|
+
def test_bit_overflow_in_expand(self):
|
|
565
|
+
"""Test that values exceeding bit width raise BitOverflowError."""
|
|
566
|
+
with self.assertRaises(self.BitOverflowError):
|
|
567
|
+
self.expand('age', '==', 256, num_bits=8) # Max for 8-bit is 255
|
|
568
|
+
|
|
569
|
+
def test_bit_overflow_in_user_attributes(self):
|
|
570
|
+
"""Test that user_attributes raises error for overflow values."""
|
|
571
|
+
with self.assertRaises(self.BitOverflowError):
|
|
572
|
+
self.helper.user_attributes({'age': 256})
|
|
573
|
+
|
|
574
|
+
def test_bit_overflow_error_in_int_to_bits(self):
|
|
575
|
+
"""Test that int_to_bits raises BitOverflowError on overflow."""
|
|
576
|
+
with self.assertRaises(self.BitOverflowError):
|
|
577
|
+
self.int_to_bits(256, 8)
|
|
578
|
+
|
|
579
|
+
def test_boundary_value_at_max(self):
|
|
580
|
+
"""Test value exactly at maximum (255 for 8-bit)."""
|
|
581
|
+
# Should work without error
|
|
582
|
+
expanded = self.expand('age', '==', 255, num_bits=8)
|
|
583
|
+
self.assertIsNotNone(expanded)
|
|
584
|
+
|
|
585
|
+
def test_boundary_value_just_over_max(self):
|
|
586
|
+
"""Test value just over maximum."""
|
|
587
|
+
with self.assertRaises(self.BitOverflowError):
|
|
588
|
+
self.expand('age', '==', 256, num_bits=8)
|
|
589
|
+
|
|
590
|
+
# --- Negative Value Tests ---
|
|
591
|
+
def test_negative_value_in_expand(self):
|
|
592
|
+
"""Test that negative values raise ValueError."""
|
|
593
|
+
with self.assertRaises(ValueError):
|
|
594
|
+
self.expand('age', '>=', -1, num_bits=8)
|
|
595
|
+
|
|
596
|
+
def test_negative_value_in_user_attributes(self):
|
|
597
|
+
"""Test that user_attributes raises error for negative values."""
|
|
598
|
+
with self.assertRaises(ValueError):
|
|
599
|
+
self.helper.user_attributes({'age': -5})
|
|
600
|
+
|
|
601
|
+
def test_negative_value_message(self):
|
|
602
|
+
"""Test that error message mentions negative values."""
|
|
603
|
+
try:
|
|
604
|
+
self.expand('age', '>=', -10, num_bits=8)
|
|
605
|
+
except ValueError as e:
|
|
606
|
+
self.assertIn('Negative', str(e))
|
|
607
|
+
|
|
608
|
+
# --- Invalid Operator Tests ---
|
|
609
|
+
def test_invalid_operator_exclamation_equal(self):
|
|
610
|
+
"""Test that != operator is rejected."""
|
|
611
|
+
with self.assertRaises(self.InvalidOperatorError):
|
|
612
|
+
self.expand('age', '!=', 21, num_bits=8)
|
|
613
|
+
|
|
614
|
+
def test_invalid_operator_not_equal_diamond(self):
|
|
615
|
+
"""Test that <> operator is rejected."""
|
|
616
|
+
with self.assertRaises(self.InvalidOperatorError):
|
|
617
|
+
self.expand('age', '<>', 21, num_bits=8)
|
|
618
|
+
|
|
619
|
+
def test_invalid_operator_tilde(self):
|
|
620
|
+
"""Test that arbitrary operators are rejected."""
|
|
621
|
+
with self.assertRaises(self.InvalidOperatorError):
|
|
622
|
+
self.expand('age', '~', 21, num_bits=8)
|
|
623
|
+
|
|
624
|
+
def test_valid_operators_all_work(self):
|
|
625
|
+
"""Test that all supported operators work."""
|
|
626
|
+
for op in ['==', '>', '>=', '<', '<=']:
|
|
627
|
+
result = self.expand('age', op, 10, num_bits=8)
|
|
628
|
+
self.assertIsNotNone(result)
|
|
629
|
+
|
|
630
|
+
# --- Invalid Bit Width Tests ---
|
|
631
|
+
def test_zero_bit_width(self):
|
|
632
|
+
"""Test that num_bits=0 raises InvalidBitWidthError."""
|
|
633
|
+
with self.assertRaises(self.InvalidBitWidthError):
|
|
634
|
+
self.validate_num_bits(0)
|
|
635
|
+
|
|
636
|
+
def test_negative_bit_width(self):
|
|
637
|
+
"""Test that negative num_bits raises InvalidBitWidthError."""
|
|
638
|
+
with self.assertRaises(self.InvalidBitWidthError):
|
|
639
|
+
self.validate_num_bits(-1)
|
|
640
|
+
|
|
641
|
+
def test_excessive_bit_width(self):
|
|
642
|
+
"""Test that num_bits > 64 raises InvalidBitWidthError."""
|
|
643
|
+
with self.assertRaises(self.InvalidBitWidthError):
|
|
644
|
+
self.validate_num_bits(65)
|
|
645
|
+
|
|
646
|
+
def test_non_integer_bit_width(self):
|
|
647
|
+
"""Test that non-integer num_bits raises InvalidBitWidthError."""
|
|
648
|
+
with self.assertRaises(self.InvalidBitWidthError):
|
|
649
|
+
self.validate_num_bits(8.5)
|
|
650
|
+
|
|
651
|
+
def test_string_bit_width(self):
|
|
652
|
+
"""Test that string num_bits raises InvalidBitWidthError."""
|
|
653
|
+
with self.assertRaises(self.InvalidBitWidthError):
|
|
654
|
+
self.validate_num_bits("8")
|
|
655
|
+
|
|
656
|
+
# --- Attribute Name Conflict Tests ---
|
|
657
|
+
def test_attribute_name_with_encoding_pattern(self):
|
|
658
|
+
"""Test that attribute names with #b# pattern are rejected."""
|
|
659
|
+
with self.assertRaises(self.AttributeNameConflictError):
|
|
660
|
+
self.validate_attribute_name('age#b0#1')
|
|
661
|
+
|
|
662
|
+
def test_attribute_name_with_partial_pattern(self):
|
|
663
|
+
"""Test attribute names with partial encoding pattern."""
|
|
664
|
+
with self.assertRaises(self.AttributeNameConflictError):
|
|
665
|
+
self.validate_attribute_name('test#b5#value')
|
|
666
|
+
|
|
667
|
+
def test_valid_attribute_names(self):
|
|
668
|
+
"""Test that normal attribute names are accepted."""
|
|
669
|
+
# These should not raise
|
|
670
|
+
self.validate_attribute_name('age')
|
|
671
|
+
self.validate_attribute_name('AGE')
|
|
672
|
+
self.validate_attribute_name('user_level')
|
|
673
|
+
self.validate_attribute_name('clearance123')
|
|
674
|
+
|
|
675
|
+
# --- Empty/Malformed Policy Tests ---
|
|
676
|
+
def test_none_policy(self):
|
|
677
|
+
"""Test that None policy raises ValueError."""
|
|
678
|
+
with self.assertRaises(ValueError):
|
|
679
|
+
self.preprocess(None, num_bits=8)
|
|
680
|
+
|
|
681
|
+
def test_empty_policy(self):
|
|
682
|
+
"""Test that empty policy returns empty string."""
|
|
683
|
+
result = self.preprocess('', num_bits=8)
|
|
684
|
+
self.assertEqual(result, '')
|
|
685
|
+
|
|
686
|
+
def test_whitespace_only_policy(self):
|
|
687
|
+
"""Test that whitespace-only policy returns empty string."""
|
|
688
|
+
result = self.preprocess(' ', num_bits=8)
|
|
689
|
+
self.assertEqual(result, '')
|
|
690
|
+
|
|
691
|
+
def test_policy_without_numeric(self):
|
|
692
|
+
"""Test that policy without numeric comparisons is unchanged."""
|
|
693
|
+
policy = 'admin and manager'
|
|
694
|
+
result = self.preprocess(policy, num_bits=8)
|
|
695
|
+
self.assertEqual(result, policy)
|
|
696
|
+
|
|
697
|
+
# --- Regex Edge Cases ---
|
|
698
|
+
def test_extra_spaces_around_operator(self):
|
|
699
|
+
"""Test numeric comparison with extra spaces."""
|
|
700
|
+
policy = 'age >= 21'
|
|
701
|
+
result = self.preprocess(policy, num_bits=8)
|
|
702
|
+
self.assertIn('#b', result)
|
|
703
|
+
self.assertNotIn('>=', result)
|
|
704
|
+
|
|
705
|
+
def test_no_spaces_around_operator(self):
|
|
706
|
+
"""Test numeric comparison without spaces."""
|
|
707
|
+
policy = 'age>=21'
|
|
708
|
+
result = self.preprocess(policy, num_bits=8)
|
|
709
|
+
self.assertIn('#b', result)
|
|
710
|
+
|
|
711
|
+
def test_mixed_spacing(self):
|
|
712
|
+
"""Test numeric comparison with mixed spacing."""
|
|
713
|
+
policy = 'age>= 21 and level <5'
|
|
714
|
+
result = self.preprocess(policy, num_bits=8)
|
|
715
|
+
self.assertIn('#b', result)
|
|
716
|
+
self.assertNotIn('>=', result)
|
|
717
|
+
self.assertNotIn('<', result)
|
|
718
|
+
|
|
719
|
+
def test_multiple_parentheses(self):
|
|
720
|
+
"""Test policy with multiple levels of parentheses."""
|
|
721
|
+
policy = '((age >= 21) and (level > 5)) or admin'
|
|
722
|
+
result = self.preprocess(policy, num_bits=8)
|
|
723
|
+
self.assertIn('admin', result)
|
|
724
|
+
self.assertIn('#b', result)
|
|
725
|
+
|
|
726
|
+
def test_attr_name_all_caps(self):
|
|
727
|
+
"""Test attribute name in all caps."""
|
|
728
|
+
policy = 'AGE >= 21'
|
|
729
|
+
result = self.preprocess(policy, num_bits=8)
|
|
730
|
+
self.assertIn('AGE#b', result)
|
|
731
|
+
|
|
732
|
+
def test_attr_name_mixed_case(self):
|
|
733
|
+
"""Test attribute name in mixed case."""
|
|
734
|
+
policy = 'Age >= 21'
|
|
735
|
+
result = self.preprocess(policy, num_bits=8)
|
|
736
|
+
self.assertIn('Age#b', result)
|
|
737
|
+
|
|
738
|
+
# --- Strict Mode Tests ---
|
|
739
|
+
def test_strict_mode_raises_on_overflow(self):
|
|
740
|
+
"""Test that strict mode raises exceptions."""
|
|
741
|
+
from charm.toolbox.ABEnumeric import preprocess_numeric_policy
|
|
742
|
+
with self.assertRaises(self.BitOverflowError):
|
|
743
|
+
preprocess_numeric_policy('age >= 256', num_bits=8, strict=True)
|
|
744
|
+
|
|
745
|
+
def test_non_strict_mode_continues_on_error(self):
|
|
746
|
+
"""Test that non-strict mode leaves problematic expression unchanged."""
|
|
747
|
+
import warnings
|
|
748
|
+
with warnings.catch_warnings(record=True) as w:
|
|
749
|
+
warnings.simplefilter("always")
|
|
750
|
+
result = self.preprocess('age >= 256', num_bits=8, strict=False)
|
|
751
|
+
# Should keep original expression and warn
|
|
752
|
+
self.assertIn('age >= 256', result)
|
|
753
|
+
self.assertGreater(len(w), 0)
|
|
754
|
+
|
|
755
|
+
def test_strict_helper_raises_on_overflow(self):
|
|
756
|
+
"""Test that helper in strict mode raises exceptions."""
|
|
757
|
+
with self.assertRaises((self.BitOverflowError, ValueError)):
|
|
758
|
+
self.strict_helper.expand_policy('age >= 256')
|
|
759
|
+
|
|
760
|
+
# --- Zero Comparison Tests ---
|
|
761
|
+
def test_greater_than_zero(self):
|
|
762
|
+
"""Test attr > 0 works correctly."""
|
|
763
|
+
result = self.expand('age', '>', 0, num_bits=8)
|
|
764
|
+
self.assertIsNotNone(result)
|
|
765
|
+
|
|
766
|
+
def test_greater_equal_zero_is_tautology(self):
|
|
767
|
+
"""Test attr >= 0 returns None (always true for non-negative)."""
|
|
768
|
+
result = self.expand('age', '>=', 0, num_bits=8)
|
|
769
|
+
# >= 0 is always true for non-negative, encode_greater_than_or_equal returns None
|
|
770
|
+
self.assertIsNone(result)
|
|
771
|
+
|
|
772
|
+
def test_less_than_zero_is_contradiction(self):
|
|
773
|
+
"""Test attr < 0 handling."""
|
|
774
|
+
result = self.expand('age', '<', 0, num_bits=8)
|
|
775
|
+
# < 0 is always false for non-negative, returns None
|
|
776
|
+
self.assertIsNone(result)
|
|
777
|
+
|
|
778
|
+
def test_equal_zero(self):
|
|
779
|
+
"""Test attr == 0 correctly encodes all zeros."""
|
|
780
|
+
result = self.expand('age', '==', 0, num_bits=8)
|
|
781
|
+
self.assertIsNotNone(result)
|
|
782
|
+
# Should have all #0 (zero bits)
|
|
783
|
+
self.assertIn('#0', result)
|
|
784
|
+
|
|
785
|
+
# --- Max Value Property Test ---
|
|
786
|
+
def test_helper_max_value_property(self):
|
|
787
|
+
"""Test that NumericAttributeHelper exposes max_value correctly."""
|
|
788
|
+
from charm.toolbox.ABEnumeric import NumericAttributeHelper
|
|
789
|
+
self.assertEqual(self.helper.max_value, 255) # 2^8 - 1 = 255
|
|
790
|
+
helper16 = NumericAttributeHelper(num_bits=16)
|
|
791
|
+
self.assertEqual(helper16.max_value, 65535) # 2^16 - 1
|
|
792
|
+
|
|
793
|
+
|
|
794
|
+
class NumericNegationTest(unittest.TestCase):
|
|
795
|
+
"""Tests for negation of numeric comparisons."""
|
|
796
|
+
|
|
797
|
+
def setUp(self):
|
|
798
|
+
from charm.toolbox.ABEnumeric import (
|
|
799
|
+
NumericAttributeHelper, negate_comparison, negate_comparison_to_policy,
|
|
800
|
+
InvalidOperatorError
|
|
801
|
+
)
|
|
802
|
+
from charm.toolbox.policytree import PolicyParser
|
|
803
|
+
self.helper = NumericAttributeHelper(num_bits=8)
|
|
804
|
+
self.negate = negate_comparison
|
|
805
|
+
self.negate_to_policy = negate_comparison_to_policy
|
|
806
|
+
self.parser = PolicyParser()
|
|
807
|
+
self.InvalidOperatorError = InvalidOperatorError
|
|
808
|
+
|
|
809
|
+
# --- Basic Negation Tests ---
|
|
810
|
+
def test_negate_greater_equal(self):
|
|
811
|
+
"""Test NOT (age >= 21) becomes age < 21."""
|
|
812
|
+
result = self.negate('age', '>=', 21)
|
|
813
|
+
self.assertEqual(result, ('age', '<', 21))
|
|
814
|
+
|
|
815
|
+
def test_negate_greater_than(self):
|
|
816
|
+
"""Test NOT (age > 21) becomes age <= 21."""
|
|
817
|
+
result = self.negate('age', '>', 21)
|
|
818
|
+
self.assertEqual(result, ('age', '<=', 21))
|
|
819
|
+
|
|
820
|
+
def test_negate_less_equal(self):
|
|
821
|
+
"""Test NOT (age <= 21) becomes age > 21."""
|
|
822
|
+
result = self.negate('age', '<=', 21)
|
|
823
|
+
self.assertEqual(result, ('age', '>', 21))
|
|
824
|
+
|
|
825
|
+
def test_negate_less_than(self):
|
|
826
|
+
"""Test NOT (age < 21) becomes age >= 21."""
|
|
827
|
+
result = self.negate('age', '<', 21)
|
|
828
|
+
self.assertEqual(result, ('age', '>=', 21))
|
|
829
|
+
|
|
830
|
+
def test_negate_equality(self):
|
|
831
|
+
"""Test NOT (age == 21) becomes (age < 21) OR (age > 21)."""
|
|
832
|
+
result = self.negate('age', '==', 21)
|
|
833
|
+
self.assertEqual(result, (('age', '<', 21), ('age', '>', 21)))
|
|
834
|
+
|
|
835
|
+
# --- Negation to Policy String Tests ---
|
|
836
|
+
def test_negate_to_policy_simple(self):
|
|
837
|
+
"""Test negate_comparison_to_policy for simple operators."""
|
|
838
|
+
self.assertEqual(self.negate_to_policy('age', '>=', 21), 'age < 21')
|
|
839
|
+
self.assertEqual(self.negate_to_policy('age', '>', 21), 'age <= 21')
|
|
840
|
+
self.assertEqual(self.negate_to_policy('age', '<=', 21), 'age > 21')
|
|
841
|
+
self.assertEqual(self.negate_to_policy('age', '<', 21), 'age >= 21')
|
|
842
|
+
|
|
843
|
+
def test_negate_to_policy_equality(self):
|
|
844
|
+
"""Test negate_comparison_to_policy for equality."""
|
|
845
|
+
result = self.negate_to_policy('age', '==', 21)
|
|
846
|
+
self.assertEqual(result, '(age < 21) or (age > 21)')
|
|
847
|
+
|
|
848
|
+
# --- Invalid Operator Tests ---
|
|
849
|
+
def test_negate_invalid_operator(self):
|
|
850
|
+
"""Test that negating invalid operators raises error."""
|
|
851
|
+
with self.assertRaises(self.InvalidOperatorError):
|
|
852
|
+
self.negate('age', '!=', 21)
|
|
853
|
+
|
|
854
|
+
# --- Helper Method Tests ---
|
|
855
|
+
def test_helper_negate_comparison(self):
|
|
856
|
+
"""Test NumericAttributeHelper.negate_comparison method."""
|
|
857
|
+
result = self.helper.negate_comparison('age', '>=', 21)
|
|
858
|
+
self.assertEqual(result, ('age', '<', 21))
|
|
859
|
+
|
|
860
|
+
def test_helper_expand_negated_policy_simple(self):
|
|
861
|
+
"""Test expand_negated_policy for simple operators."""
|
|
862
|
+
# NOT (age >= 21) should expand to bit encoding of age < 21
|
|
863
|
+
result = self.helper.expand_negated_policy('age', '>=', 21)
|
|
864
|
+
self.assertIsNotNone(result)
|
|
865
|
+
self.assertIn('#b', result)
|
|
866
|
+
|
|
867
|
+
def test_helper_expand_negated_policy_equality(self):
|
|
868
|
+
"""Test expand_negated_policy for equality."""
|
|
869
|
+
# NOT (age == 21) should expand to (age < 21) OR (age > 21)
|
|
870
|
+
result = self.helper.expand_negated_policy('age', '==', 21)
|
|
871
|
+
self.assertIsNotNone(result)
|
|
872
|
+
self.assertIn(' or ', result)
|
|
873
|
+
self.assertIn('#b', result)
|
|
874
|
+
|
|
875
|
+
# --- End-to-End Negation Tests ---
|
|
876
|
+
def test_negated_policy_satisfaction(self):
|
|
877
|
+
"""Test that negated policies work correctly end-to-end."""
|
|
878
|
+
# Original: age >= 21 (user with age 20 should NOT satisfy)
|
|
879
|
+
# Negated: age < 21 (user with age 20 SHOULD satisfy)
|
|
880
|
+
|
|
881
|
+
negated_policy = self.negate_to_policy('age', '>=', 21)
|
|
882
|
+
expanded = self.helper.expand_policy(negated_policy)
|
|
883
|
+
tree = self.parser.parse(expanded)
|
|
884
|
+
|
|
885
|
+
# User with age 20 should satisfy "age < 21"
|
|
886
|
+
user_attrs = self.helper.user_attributes({'age': 20})
|
|
887
|
+
self.assertTrue(self.parser.prune(tree, user_attrs))
|
|
888
|
+
|
|
889
|
+
# User with age 21 should NOT satisfy "age < 21"
|
|
890
|
+
user_attrs = self.helper.user_attributes({'age': 21})
|
|
891
|
+
self.assertFalse(self.parser.prune(tree, user_attrs))
|
|
892
|
+
|
|
893
|
+
# User with age 25 should NOT satisfy "age < 21"
|
|
894
|
+
user_attrs = self.helper.user_attributes({'age': 25})
|
|
895
|
+
self.assertFalse(self.parser.prune(tree, user_attrs))
|
|
896
|
+
|
|
897
|
+
def test_negated_equality_satisfaction(self):
|
|
898
|
+
"""Test that negated equality works correctly end-to-end."""
|
|
899
|
+
# NOT (age == 21) means age != 21, i.e., (age < 21) OR (age > 21)
|
|
900
|
+
|
|
901
|
+
negated_policy = self.negate_to_policy('age', '==', 21)
|
|
902
|
+
expanded = self.helper.expand_policy(negated_policy)
|
|
903
|
+
tree = self.parser.parse(expanded)
|
|
904
|
+
|
|
905
|
+
# User with age 20 should satisfy "age != 21"
|
|
906
|
+
user_attrs = self.helper.user_attributes({'age': 20})
|
|
907
|
+
self.assertTrue(self.parser.prune(tree, user_attrs))
|
|
908
|
+
|
|
909
|
+
# User with age 21 should NOT satisfy "age != 21"
|
|
910
|
+
user_attrs = self.helper.user_attributes({'age': 21})
|
|
911
|
+
self.assertFalse(self.parser.prune(tree, user_attrs))
|
|
912
|
+
|
|
913
|
+
# User with age 22 should satisfy "age != 21"
|
|
914
|
+
user_attrs = self.helper.user_attributes({'age': 22})
|
|
915
|
+
self.assertTrue(self.parser.prune(tree, user_attrs))
|
|
916
|
+
|
|
917
|
+
# --- Boundary Value Tests ---
|
|
918
|
+
def test_negate_at_zero(self):
|
|
919
|
+
"""Test negation at zero boundary."""
|
|
920
|
+
# NOT (age >= 0) = age < 0 (always false for non-negative)
|
|
921
|
+
result = self.negate('age', '>=', 0)
|
|
922
|
+
self.assertEqual(result, ('age', '<', 0))
|
|
923
|
+
|
|
924
|
+
# NOT (age > 0) = age <= 0 (only true for 0)
|
|
925
|
+
result = self.negate('age', '>', 0)
|
|
926
|
+
self.assertEqual(result, ('age', '<=', 0))
|
|
927
|
+
|
|
928
|
+
def test_negate_at_max(self):
|
|
929
|
+
"""Test negation at max value boundary."""
|
|
930
|
+
# NOT (age <= 255) = age > 255 (always false for 8-bit)
|
|
931
|
+
result = self.negate('age', '<=', 255)
|
|
932
|
+
self.assertEqual(result, ('age', '>', 255))
|
|
933
|
+
|
|
934
|
+
|
|
935
|
+
def run_stress_test():
|
|
936
|
+
"""Run the stress test suite and print results."""
|
|
937
|
+
print("=" * 70)
|
|
938
|
+
print("ABE Policy Parser Stress Test")
|
|
939
|
+
print("=" * 70)
|
|
940
|
+
print()
|
|
941
|
+
|
|
942
|
+
# Run tests
|
|
943
|
+
loader = unittest.TestLoader()
|
|
944
|
+
suite = unittest.TestSuite()
|
|
945
|
+
|
|
946
|
+
suite.addTests(loader.loadTestsFromTestCase(PolicyParserStressTest))
|
|
947
|
+
suite.addTests(loader.loadTestsFromTestCase(PolicyParserEdgeCaseTest))
|
|
948
|
+
suite.addTests(loader.loadTestsFromTestCase(NumericAttributeTest))
|
|
949
|
+
suite.addTests(loader.loadTestsFromTestCase(NumericAttributeEdgeCaseTest))
|
|
950
|
+
suite.addTests(loader.loadTestsFromTestCase(NumericNegationTest))
|
|
951
|
+
|
|
952
|
+
runner = unittest.TextTestRunner(verbosity=2)
|
|
953
|
+
result = runner.run(suite)
|
|
954
|
+
|
|
955
|
+
print()
|
|
956
|
+
print("=" * 70)
|
|
957
|
+
if result.wasSuccessful():
|
|
958
|
+
print("ALL TESTS PASSED")
|
|
959
|
+
else:
|
|
960
|
+
print(f"FAILURES: {len(result.failures)}, ERRORS: {len(result.errors)}")
|
|
961
|
+
print("=" * 70)
|
|
962
|
+
|
|
963
|
+
return result.wasSuccessful()
|
|
964
|
+
|
|
965
|
+
|
|
966
|
+
if __name__ == '__main__':
|
|
967
|
+
success = run_stress_test()
|
|
968
|
+
sys.exit(0 if success else 1)
|
|
969
|
+
|