@@ -27,6 +27,7 @@ def parse_features():
2727 # Group features (features that contain other features)
2828 grouped_features = {}
2929 grouped_sub_features = set ()
30+ child_features = set ()
3031
3132 # First pass: identify grouped features and their sub-features
3233 for feature_name , feature_deps in features .items ():
@@ -37,6 +38,7 @@ def parse_features():
3738 # This is a grouped feature
3839 grouped_features [feature_name ] = feature_deps
3940 grouped_sub_features .update (feature_deps )
41+ child_features .update (feature_deps )
4042
4143 # Second pass: identify standalone features (not part of any group)
4244 standalone_features = []
@@ -46,7 +48,7 @@ def parse_features():
4648 feature_name not in grouped_sub_features ):
4749 standalone_features .append (feature_name )
4850
49- return grouped_features , standalone_features
51+ return grouped_features , standalone_features , child_features
5052
5153# Default test suite - always required for cargo test
5254DEFAULT_TESTSUITE = "sanity"
@@ -157,6 +159,16 @@ def run_single_cargo_test(feature, test_suite, binary_path="q", quiet=False):
157159 "individual_tests" : individual_tests
158160 }
159161
162+ def validate_features (features ):
163+ """Validate that all features exist in Cargo.toml"""
164+ grouped_features , standalone_features , child_features = parse_features ()
165+ valid_features = set (grouped_features .keys ()) | set (standalone_features ) | child_features
166+ invalid_features = [f for f in features if f not in valid_features and f not in {"sanity" , "regression" }]
167+ if invalid_features :
168+ print (f"❌ Error: Invalid feature(s): { ', ' .join (invalid_features )} " )
169+ print (f"Available features: { ', ' .join (sorted (valid_features ))} " )
170+ sys .exit (1 )
171+
160172def get_test_suites_from_features (features ):
161173 """Extract test suites (sanity/regression) from feature list"""
162174 test_suites = []
@@ -165,6 +177,11 @@ def get_test_suites_from_features(features):
165177 if "regression" in features :
166178 test_suites .append ("regression" )
167179
180+ # Check if both sanity and regression are specified
181+ if len (test_suites ) > 1 :
182+ print ("❌ Error: Only a single test suite is allowed. Cannot run both 'sanity' and 'regression' together." )
183+ sys .exit (1 )
184+
168185 if not test_suites :
169186 test_suites = [DEFAULT_TESTSUITE ]
170187
@@ -271,7 +288,7 @@ def generate_report(results, features, test_suites, binary_path="q"):
271288
272289 # Generate filename with features and test suites
273290 # If running all features (sanity/regression mode), use only test suite names
274- grouped_features , standalone_features = parse_features ()
291+ grouped_features , standalone_features , _ = parse_features ()
275292 all_available_features = list (grouped_features .keys ()) + standalone_features
276293
277294 if set (features ) == set (all_available_features ):
@@ -448,7 +465,7 @@ def dev_debug():
448465 print ("🔧 Developer Debug Mode" )
449466 print ("=" * 30 )
450467
451- grouped_features , standalone_features = parse_features ()
468+ grouped_features , standalone_features , child_features = parse_features ()
452469
453470 print ("\n 📦 Grouped Features:" )
454471 for group , deps in grouped_features .items ():
@@ -458,10 +475,15 @@ def dev_debug():
458475 for feature in standalone_features :
459476 print (f" { feature } " )
460477
478+ print (f"\n 👶 Child Features:" )
479+ for feature in sorted (child_features ):
480+ print (f" { feature } " )
481+
461482 print (f"\n 📊 Summary:" )
462483 print (f" Grouped: { len (grouped_features )} " )
463484 print (f" Standalone: { len (standalone_features )} " )
464- print (f" Total: { len (grouped_features ) + len (standalone_features )} " )
485+ print (f" Child: { len (child_features )} " )
486+ print (f" Total: { len (grouped_features ) + len (standalone_features ) + len (child_features )} " )
465487
466488def main ():
467489 parser = argparse .ArgumentParser (
@@ -476,7 +498,7 @@ def main():
476498 %(prog)s --features sanity # Run all tests with sanity suite
477499 %(prog)s --features regression # Run all tests with regression suite
478500 %(prog)s --features "usage,regression" # Run usage tests with regression suite
479- %(prog)s --features "sanity,regression" # Run all tests with both suites
501+
480502
481503 # Multiple features (different ways)
482504 %(prog)s --features "usage,agent,context" # Comma-separated features
@@ -529,20 +551,21 @@ def main():
529551
530552 if not args .features :
531553 # Run all features with default test suite
532- grouped_features , standalone_features = parse_features ()
554+ grouped_features , standalone_features , _ = parse_features ()
533555 all_features = list (grouped_features .keys ()) + standalone_features
534556 test_suites = [DEFAULT_TESTSUITE ]
535557 else :
536558 # Parse requested features
537559 requested_features = [f .strip () for f in args .features .split ("," )]
560+ validate_features (requested_features )
538561 test_suites = get_test_suites_from_features (requested_features )
539562
540563 # Remove test suites from feature list
541564 features_only = [f for f in requested_features if f not in {"sanity" , "regression" }]
542565
543566 if not features_only :
544567 # Only sanity/regression specified - run all features
545- grouped_features , standalone_features = parse_features ()
568+ grouped_features , standalone_features , _ = parse_features ()
546569 all_features = list (grouped_features .keys ()) + standalone_features
547570 else :
548571 all_features = features_only
0 commit comments