The orcapod-python codebase has grown complex with many interdependent components. Existing tests were often written by the same agent that implemented the code, risking "self-affirmation" — tests that validate what was built rather than what was specified. This plan creates an independent test suite derived purely from design documents, protocol definitions, and interface contracts, organized in a new test-objective/ root folder.
Tests are derived from these specification sources (NOT from reading implementation code):
orcapod-design.md— the canonical design specification- Protocol definitions in
src/orcapod/protocols/— interface contracts - Type annotations and docstrings — method signatures and documented behavior
CLAUDE.mdarchitecture overview — documented invariants and constraintsDESIGN_ISSUES.md— known bugs that tests should catch
test-objective/
├── conftest.py # Shared fixtures (sources, streams, functions)
├── unit/
│ ├── __init__.py
│ ├── test_types.py # Schema, ColumnConfig, ContentHash
│ ├── test_datagram.py # Datagram core behavior
│ ├── test_tag.py # Tag (system tags, ColumnConfig filtering)
│ ├── test_data.py # Data (source info, provenance)
│ ├── test_stream.py # ArrowTableStream construction & iteration
│ ├── test_sources.py # All source types + error conditions
│ ├── test_source_registry.py # SourceRegistry CRUD + edge cases
│ ├── test_data_function.py # PythonDataFunction + CachedDataFunction
│ ├── test_function_pod.py # FunctionPod, FunctionPodStream
│ ├── test_operators.py # All operators (Join, MergeJoin, SemiJoin, etc.)
│ ├── test_nodes.py # FunctionNode, OperatorNode, Persistent variants
│ ├── test_hashing.py # SemanticHasher, TypeHandlerRegistry, handlers
│ ├── test_databases.py # InMemory, DeltaLake, NoOp databases
│ ├── test_schema_utils.py # Schema extraction, union, intersection
│ ├── test_arrow_utils.py # Arrow table/schema utilities
│ ├── test_arrow_data_utils.py # System tags, source info, column helpers
│ ├── test_semantic_types.py # UniversalTypeConverter, SemanticTypeRegistry
│ ├── test_contexts.py # DataContext resolution, validation
│ ├── test_tracker.py # BasicTrackerManager, GraphTracker
│ └── test_lazy_module.py # LazyModule deferred import behavior
├── integration/
│ ├── __init__.py
│ ├── test_pipeline_flows.py # End-to-end pipeline scenarios
│ ├── test_caching_flows.py # DB-backed caching (FunctionNode, OperatorNode)
│ ├── test_hash_invariants.py # Hash stability & Merkle chain properties
│ ├── test_provenance.py # System tag lineage through pipelines
│ └── test_column_config_filtering.py # ColumnConfig behavior across all components
└── property/
├── __init__.py
├── test_schema_properties.py # Hypothesis-based schema algebra
├── test_hash_properties.py # Hash determinism, collision resistance
└── test_operator_algebra.py # Commutativity, associativity, idempotency
Schema:
test_schema_construction_from_dict— Schema({"a": int, "b": str}) stores correct fieldstest_schema_construction_with_kwargs— Schema(fields, x=int) merges kwargs with precedencetest_schema_optional_fields— optional_fields stored as frozenset, not in required_fieldstest_schema_required_fields— required_fields = all fields minus optional_fieldstest_schema_immutability— Schema is an immutable Mapping (no setitem)test_schema_merge_compatible— Schema.merge() combines non-conflicting schemastest_schema_merge_type_conflict_raises— Schema.merge() raises ValueError on type conflictstest_schema_with_values_overrides_silently— with_values() overrides without errorstest_schema_select_existing_fields— select() returns subsettest_schema_select_missing_field_raises— select() raises KeyError on missing fieldtest_schema_drop_existing_fields— drop() removes fieldstest_schema_drop_missing_field_silent— drop() silently ignores missing fieldstest_schema_is_compatible_with_superset— returns True when other is supersettest_schema_is_not_compatible_with_subset— returns False when other is subsettest_schema_empty— Schema.empty() returns zero-field schematest_schema_mapping_interface— getitem, contains, iter, len work correctly
ContentHash:
test_content_hash_immutability— frozen dataclass, cannot reassign method/digesttest_content_hash_to_hex— to_hex(8) returns 8-char hex stringtest_content_hash_to_int— to_int() returns consistent integertest_content_hash_to_uuid— to_uuid() returns deterministic UUIDtest_content_hash_to_base64— to_base64() returns valid base64test_content_hash_to_string_and_from_string_roundtrip— from_string(to_string()) == originaltest_content_hash_display_name— display_name() returns "method:short_hex" formattest_content_hash_equality— same method+digest are equaltest_content_hash_inequality— different digests are not equal
ColumnConfig:
test_column_config_defaults— all fields False by defaulttest_column_config_all— ColumnConfig.all() sets everything Truetest_column_config_data_only— ColumnConfig.data_only() sets everything Falsetest_column_config_handle_config_dict— handle_config(dict) normalizes to ColumnConfigtest_column_config_handle_config_all_info_override— all_info=True overrides individual fieldstest_column_config_frozen— cannot modify after construction
Construction:
test_datagram_from_dict— construct from Python dicttest_datagram_from_arrow_table— construct from pa.Tabletest_datagram_from_record_batch— construct from pa.RecordBatchtest_datagram_with_meta_info— meta columns stored separatelytest_datagram_with_python_schema— explicit schema used over inferencetest_datagram_with_record_id— custom record_id stored as datagram_id
Dict-like Access:
test_datagram_getitem_existing_key— returns correct valuetest_datagram_getitem_missing_key_raises— raises KeyErrortest_datagram_contains— contains returns True/False correctlytest_datagram_iter— iter yields all data column namestest_datagram_get_with_default— get() returns default for missing keys
Lazy Conversion (key invariant):
test_datagram_dict_access_uses_dict_backing— dict access doesn't trigger Arrow conversiontest_datagram_as_table_triggers_arrow_conversion— as_table() produces Arrow tabletest_datagram_dict_arrow_roundtrip_preserves_data— dict→Arrow→dict preserves valuestest_datagram_arrow_dict_roundtrip_preserves_data— Arrow→dict→Arrow preserves values
Schema Methods:
test_datagram_keys_data_only— keys() returns only data column names by defaulttest_datagram_keys_all_info— keys(all_info=True) includes meta columnstest_datagram_schema_matches_keys— schema() field names match keys()test_datagram_arrow_schema_type_consistency— arrow_schema() types match schema() types
Format Conversions:
test_datagram_as_dict— returns plain Python dicttest_datagram_as_table— returns single-row pa.Tabletest_datagram_as_arrow_compatible_dict— values are Arrow-compatible
Data Operations (immutability):
test_datagram_select_returns_new_instance— original unchangedtest_datagram_drop_returns_new_instance— original unchangedtest_datagram_rename_returns_new_instance— original unchangedtest_datagram_update_existing_columns_only— update() only changes existing columnstest_datagram_with_columns_new_only— with_columns() only adds new columnstest_datagram_copy_creates_independent_copy— mutations to copy don't affect original
Meta Operations:
test_datagram_get_meta_value_auto_prefixed— get_meta_value() auto-adds prefixtest_datagram_with_meta_columns_returns_new— immutable updatetest_datagram_drop_meta_columns_returns_new— immutable drop
Content Hashing:
test_datagram_content_hash_deterministic— same data → same hashtest_datagram_content_hash_changes_with_data— different data → different hashtest_datagram_equality_by_content— equal content → equal datagrams
test_tag_construction_with_system_tags— system tags stored separately from datatest_tag_system_tags_excluded_from_default_keys— keys() doesn't show system tagstest_tag_system_tags_included_with_column_config— keys(columns={"system_tags": True}) shows themtest_tag_as_dict_excludes_system_tags_by_default— as_dict() only has datatest_tag_as_dict_all_info_includes_system_tags— as_dict(all_info=True) has everythingtest_tag_as_table_excludes_system_tags_by_defaulttest_tag_as_table_all_info_includes_system_tagstest_tag_schema_excludes_system_tags_by_defaulttest_tag_copy_preserves_system_tags— copy() includes system tagstest_tag_as_datagram_conversion— as_datagram() returns Datagram (not Tag)test_tag_system_tags_method_returns_copy— system_tags() returns dict copy, not reference
test_data_construction_with_source_info— source_info stored per data columntest_data_source_info_excluded_from_default_keys— keys() doesn't show source columnstest_data_source_info_included_with_column_config— keys(columns={"source": True})test_data_with_source_info_returns_new— immutable updatetest_data_rename_updates_source_info_keys— rename() also renames source_info keystest_data_with_columns_adds_source_info_entry— new columns get source_info=Nonetest_data_as_datagram_conversion— as_datagram() returns Datagramtest_data_as_dict_excludes_source_columns_by_defaulttest_data_as_dict_all_info_includes_source_columnstest_data_copy_preserves_source_info
Construction:
test_stream_from_table_with_tag_columns— tag/data column separationtest_stream_requires_at_least_one_data_column— ValueError if no data columnstest_stream_with_system_tag_columns— system tag columns trackedtest_stream_with_source_info— source info attached to data columnstest_stream_with_producer— producer property settest_stream_with_upstreams— upstreams tuple set
Schema & Keys:
test_stream_keys_returns_tag_and_data_keys— tuple of (tag_keys, data_keys)test_stream_output_schema_returns_two_schemas— (tag_schema, data_schema)test_stream_schema_matches_actual_data— output_schema() types match as_table() typestest_stream_keys_with_column_config— ColumnConfig filtering works
Iteration:
test_stream_iter_data_yields_tag_data_pairs— each yield is (Tag, Data)test_stream_iter_data_count_matches_rows— number of yields = number of rowstest_stream_iter_data_tag_keys_correct— tag column names matchtest_stream_iter_data_data_keys_correct— data column names matchtest_stream_as_table_matches_iter_data— table materialization consistent with iteration
Immutability:
test_stream_immutable— no mutation methods available
Format Conversions:
test_stream_as_polars_df— converts to Polars DataFrametest_stream_as_pandas_df— converts to Pandas DataFrametest_stream_as_lazy_frame— converts to Polars LazyFrame
ArrowTableSource:
test_arrow_source_from_valid_table— normal construction succeedstest_arrow_source_empty_table_raises— ValueError("Table is empty")test_arrow_source_missing_tag_column_raises— ValueError if tag_columns not in tabletest_arrow_source_adds_system_tag_column— system tag column added automaticallytest_arrow_source_adds_source_info_columns— source columns addedtest_arrow_source_source_id_set— source_id property populatedtest_arrow_source_producer_is_none— root sources have no producertest_arrow_source_upstreams_empty— root sources have no upstreamstest_arrow_source_resolve_field_by_record_id— resolves field valuetest_arrow_source_resolve_field_missing_raises— FieldNotResolvableErrortest_arrow_source_pipeline_identity_structure— returns (tag_schema, data_schema)test_arrow_source_iter_data_yields_correct_pairstest_arrow_source_as_table_has_all_columns
DictSource:
test_dict_source_from_dict_of_lists— constructs correctlytest_dict_source_delegates_to_arrow_table_source— same behavior as ArrowTableSourcetest_dict_source_with_tag_columns
ListSource:
test_list_source_from_list_of_dicts— constructs correctlytest_list_source_empty_list_raises— ValueError
CSVSource:
test_csv_source_from_file— reads CSV correctlytest_csv_source_with_tag_columns
DataFrameSource:
test_dataframe_source_from_polars— constructs from Polars DataFrametest_dataframe_source_from_pandas— constructs from Pandas DataFrame
DerivedSource:
test_derived_source_before_run_raises— ValueError before upstream has computedtest_derived_source_after_run_yields_records— produces records from upstream node
test_registry_register_and_get— register then retrievetest_registry_register_empty_id_raises— ValueErrortest_registry_register_none_source_raises— ValueErrortest_registry_register_same_object_idempotent— re-register same object is no-optest_registry_register_different_object_same_id_keeps_existing— warns, keeps existingtest_registry_replace_overwrites— replace() unconditionally overwritestest_registry_replace_returns_old— returns previous sourcetest_registry_unregister_removes— removes and returns sourcetest_registry_unregister_missing_raises— KeyErrortest_registry_get_missing_raises— KeyErrortest_registry_get_optional_missing_returns_none— returns Nonetest_registry_contains— contains workstest_registry_len— len workstest_registry_iter— iter yields IDstest_registry_clear— removes all entriestest_registry_list_ids— returns list of registered IDs
PythonDataFunction:
test_pf_from_simple_function— wraps a function with explicit output_keystest_pf_infers_input_schema_from_signature— type annotations → input_data_schematest_pf_infers_output_schema— output type annotations or output_keys → output_data_schematest_pf_rejects_variadic_parameters— *args, **kwargs raise ValueErrortest_pf_call_transforms_data— call() applies function to data datatest_pf_call_returns_none_if_function_returns_none— None propagatestest_pf_direct_call_bypasses_executor— direct_call() ignores executortest_pf_call_routes_through_executor— call() uses executor when settest_pf_version_parsing— "v1.2" → major_version=1, minor_version_string="2"test_pf_canonical_function_name— uses function.name or explicit nametest_pf_content_hash_deterministic— same function → same hashtest_pf_content_hash_changes_with_function— different function → different hashtest_pf_pipeline_hash_ignores_data— pipeline_hash based on schema only
CachedDataFunction:
test_cached_pf_cache_miss_computes_and_stores— first call computes + recordstest_cached_pf_cache_hit_returns_stored— second call returns cached resulttest_cached_pf_skip_cache_lookup_always_computes— skip_cache_lookup=True forces computetest_cached_pf_skip_cache_insert_doesnt_store— skip_cache_insert=True skips recordingtest_cached_pf_get_all_cached_outputs— returns all stored records as tabletest_cached_pf_record_path_based_on_function_hash— record path includes function identity
FunctionPod:
test_function_pod_process_returns_stream— process() returns FunctionPodStreamtest_function_pod_validate_inputs_single_stream— accepts exactly one streamtest_function_pod_validate_inputs_multiple_raises— rejects multiple streamstest_function_pod_output_schema_prediction— output_schema() matches actual outputtest_function_pod_callable_alias— call same as process()test_function_pod_never_modifies_tags— tags pass through unchangedtest_function_pod_transforms_data— data are transformed by function
FunctionPodStream:
test_fps_lazy_evaluation— iter_data() triggers computationtest_fps_producer_is_function_pod— producer property returns the podtest_fps_upstreams_contains_input_streamtest_fps_keys_matches_pod_output_schema— keys() consistent with pod.output_schema()test_fps_as_table_materialization— as_table() returns correct tabletest_fps_clear_cache_forces_recompute— clear_cache() resets cached state
Decorator:
test_function_pod_decorator_creates_pod_attribute— @function_pod adds .podtest_function_pod_decorator_with_result_database— wraps in CachedDataFunction
Join (N-ary, commutative):
test_join_two_streams_on_common_tags— inner join on shared tag columnstest_join_non_overlapping_data_columns_required— InputValidationError on collisiontest_join_commutative— join(A, B) == join(B, A) (same rows regardless of order)test_join_three_or_more_streams— N-ary join workstest_join_empty_result_when_no_matches— disjoint tags → empty streamtest_join_system_tag_name_extending— system tag columns get ::pipeline_hash:position suffixtest_join_system_tag_values_sorted_for_commutativity— canonical ordering of tag valuestest_join_output_schema_prediction— output_schema() matches actual output
MergeJoin (binary):
test_merge_join_colliding_columns_become_sorted_lists— same-name data cols → list[T]test_merge_join_requires_identical_types— different types raise errortest_merge_join_non_colliding_columns_pass_through— unmatched columns kept as-istest_merge_join_system_tag_name_extendingtest_merge_join_output_schema_prediction— predicts list[T] types correctly
SemiJoin (binary, non-commutative):
test_semijoin_filters_left_by_right_tags— keeps left rows matching right tagstest_semijoin_non_commutative— semijoin(A, B) != semijoin(B, A) in generaltest_semijoin_preserves_left_data_columns— right data columns droppedtest_semijoin_system_tag_name_extending
Batch:
test_batch_groups_rows— groups rows by tag, aggregates datatest_batch_types_become_lists— data column types become list[T]test_batch_system_tag_type_evolving— system tag type becomes list[str]test_batch_with_batch_size— batch_size limits group sizetest_batch_drop_partial_batch— drop_partial_batch=True drops incomplete groupstest_batch_output_schema_prediction— predicts list[T] types
Column Selection (Select/Drop Tag/Data):
test_select_tag_columns— keeps only specified tag columnstest_select_tag_columns_strict_missing_raises— strict=True raises on missing columntest_select_data_columns— keeps only specified data columnstest_drop_tag_columns— removes specified tag columnstest_drop_data_columns— removes specified data columnstest_column_selection_system_tag_name_preserving— system tags unchanged
MapTags/MapData:
test_map_tags_renames_tag_columns— renames specified tag columnstest_map_tags_drop_unmapped— drop_unmapped=True removes unrenamed columnstest_map_data_renames_data_columnstest_map_preserves_system_tags— system tag columns unchanged (name-preserving)
PolarsFilter:
test_polars_filter_with_predicate— filters rows matching predicatetest_polars_filter_with_constraints— filters by column=value constraintstest_polars_filter_preserves_schema— output schema same as inputtest_polars_filter_system_tag_name_preserving
Operator Base Classes:
test_unary_operator_rejects_multiple_inputs— validate_inputs raises for >1 streamtest_binary_operator_rejects_wrong_count— validate_inputs raises for !=2 streamstest_nonzero_input_operator_rejects_zero— validate_inputs raises for 0 streams
FunctionNode:
test_function_node_iter_data— iterates and transforms all datatest_function_node_process_data— transforms single (tag, data) pairtest_function_node_producer_is_function_podtest_function_node_upstreamstest_function_node_clear_cache
PersistentFunctionNode:
test_persistent_fn_two_phase_iteration— Phase 1: cached records, Phase 2: compute missingtest_persistent_fn_pipeline_path_uses_pipeline_hash— path includes pipeline_hashtest_persistent_fn_caches_computed_results— computed results stored in DBtest_persistent_fn_skips_already_cached— Phase 2 skips inputs with cached outputstest_persistent_fn_run_eagerly_processes_all— run() processes all datatest_persistent_fn_as_source_returns_derived_source— as_source() returns DerivedSource
OperatorNode:
test_operator_node_delegates_to_operatortest_operator_node_clear_cachetest_operator_node_run
PersistentOperatorNode:
test_persistent_on_cache_mode_off— always recomputestest_persistent_on_cache_mode_log— computes and storestest_persistent_on_cache_mode_replay— loads from DB, no recomputetest_persistent_on_as_source_returns_derived_source
BaseSemanticHasher:
test_hasher_primitives— int, str, float, bool, None hashed deterministicallytest_hasher_structures— list, dict, tuple, set expanded structurallytest_hasher_content_hash_terminal— ContentHash inputs returned as-istest_hasher_content_identifiable_uses_identity_structure— resolves via identity_structure()test_hasher_unknown_type_strict_raises— TypeError in strict modetest_hasher_deterministic— same input → same hash alwaystest_hasher_different_inputs_different_hashes— collision resistancetest_hasher_nested_structures— deeply nested dicts/lists hashed correctly
TypeHandlerRegistry:
test_registry_register_and_lookup— register handler, get_handler returns ittest_registry_mro_aware_lookup— subclass falls back to parent handlertest_registry_unregister— remove handlertest_registry_has_handler— boolean checktest_registry_registered_types— list all registered typestest_registry_thread_safety— concurrent register/lookup doesn't crash
Built-in Handlers:
test_path_handler_hashes_file_content— Path → file content hashtest_path_handler_missing_file_raises— FileNotFoundErrortest_uuid_handler— UUID → canonical stringtest_bytes_handler— bytes → hex stringtest_function_handler— function → signature-based identitytest_type_object_handler— type → "type:module.qualname"test_arrow_table_handler— pa.Table → content hash
InMemoryArrowDatabase:
test_inmemory_add_and_get_record— add_record + get_record_by_id roundtriptest_inmemory_add_records_batch— add_records with multiple rowstest_inmemory_get_all_records— returns all at pathtest_inmemory_get_records_by_ids— returns subset by IDstest_inmemory_skip_duplicates— skip_duplicates=True doesn't raisetest_inmemory_pending_batch_semantics— records not visible before flush()test_inmemory_flush_makes_visible— flush() commits pending recordstest_inmemory_invalid_path_raises— ValueError for empty/invalid pathstest_inmemory_get_nonexistent_returns_none— missing path → None
NoOpArrowDatabase:
test_noop_all_writes_silently_discarded— add_record/add_records don't errortest_noop_all_reads_return_none— get_* always returns Nonetest_noop_flush_noop— flush() doesn't error
DeltaTableDatabase (if available):
test_delta_add_and_get_record— persistence roundtriptest_delta_flush_writes_to_disk— data survives flushtest_delta_path_validation— invalid paths rejected
test_extract_function_schemas_from_annotations— infers schemas from type hintstest_extract_function_schemas_rejects_variadic— ValueError for *args/**kwargstest_verify_data_schema_valid— matching dict passestest_verify_data_schema_type_mismatch— mismatched types failtest_check_schema_compatibility— compatible types passtest_infer_schema_from_dict— infers types from valuestest_union_schemas_no_conflict— merges cleanlytest_union_schemas_with_conflict_raises— TypeError on conflicting typestest_intersection_schemas— returns common fieldstest_get_compatible_type_int_float— numeric promotiontest_get_compatible_type_incompatible_raises— TypeError
test_schema_select— selects subset of arrow schema columnstest_schema_select_missing_raises— KeyError for missing columnstest_schema_drop— drops specified columnstest_normalize_to_large_types— string → large_string, etc.test_pylist_to_pydict— row-oriented → column-orientedtest_pydict_to_pylist— column-oriented → row-orientedtest_pydict_to_pylist_inconsistent_lengths_raises— ValueErrortest_hstack_tables— horizontal concatenationtest_hstack_tables_different_row_counts_raises— ValueErrortest_hstack_tables_duplicate_columns_raises— ValueErrortest_check_arrow_schema_compatibility— compatible schemas passtest_split_by_column_groups— splits table into multiple tables
test_add_system_tag_columns— adds _tag:: prefixed columnstest_add_system_tag_columns_empty_table_raises— ValueErrortest_add_system_tag_columns_length_mismatch_raises— ValueErrortest_append_to_system_tags— extends existing system tag valuestest_sort_system_tag_values— canonical sorting for commutativitytest_add_source_info— adds source prefixed columnstest_drop_columns_with_prefix— removes columns matching prefixtest_drop_system_columns— removes __ and __ prefixed columns
test_python_to_arrow_type_primitives— int→int64, str→large_string, etc.test_python_to_arrow_type_list— list[int]→large_list(int64)test_python_to_arrow_type_dict— dict→structtest_arrow_to_python_type_roundtrip— python→arrow→python recovers originaltest_python_dicts_to_arrow_table— list of dicts → pa.Tabletest_arrow_table_to_python_dicts— pa.Table → list of dictstest_schema_conversion_roundtrip— Schema→pa.Schema→Schema preserves types
test_resolve_context_none_returns_default— None → default contexttest_resolve_context_string_version— "v0.1" → matching contexttest_resolve_context_datacontext_passthrough— DataContext returned as-istest_resolve_context_invalid_raises— ContextResolutionErrortest_get_available_contexts— returns sorted version listtest_default_context_has_all_components— type_converter, arrow_hasher, semantic_hasher present
test_tracker_manager_register_deregister— add/remove trackerstest_tracker_manager_broadcasts_invocations— records sent to all active trackerstest_tracker_manager_no_tracking_context— no_tracking() suspends recordingtest_graph_tracker_records_function_pod_invocation— node added to graphtest_graph_tracker_records_operator_invocation— node added to graphtest_graph_tracker_compile_builds_graph— compile() produces nx.DiGraphtest_graph_tracker_reset_clears_state
test_lazy_module_not_loaded_initially— is_loaded is Falsetest_lazy_module_loads_on_attribute_access— accessing attr triggers importtest_lazy_module_force_load— force_load() triggers immediate importtest_lazy_module_invalid_module_raises— ModuleNotFoundError
test_source_to_stream_to_single_operator— Source → Filter → Streamtest_source_to_function_pod— Source → FunctionPod → Stream with transformed datatest_multi_source_join— Two sources → Join → Stream with combined datatest_chained_operators— Source → Filter → Select → MapTags → Streamtest_function_pod_then_operator— Source → FunctionPod → Filter → Streamtest_join_then_batch— Two sources → Join → Batch → Streamtest_semijoin_filters_correctly— Source A semi-joined with Source Btest_merge_join_combines_columns— Two sources with overlapping columns → MergeJointest_diamond_pipeline— Source → [branch A, branch B] → Join → Streamtest_pipeline_with_multiple_function_pods— Source → FunctionPod1 → FunctionPod2
test_persistent_function_node_caches_and_replays— first run computes, second replaystest_persistent_function_node_incremental_update— new input rows only compute missingtest_persistent_operator_node_log_mode— CacheMode.LOG stores resultstest_persistent_operator_node_replay_mode— CacheMode.REPLAY loads from DBtest_derived_source_reingestion— PersistentFunctionNode → DerivedSource → further pipelinetest_cached_data_function_with_inmemory_db— end-to-end caching flow
test_content_hash_stability_same_data— identical data → identical hash across runstest_content_hash_changes_with_data— different data → different hashtest_pipeline_hash_ignores_data_content— same schema, different data → same pipeline_hashtest_pipeline_hash_changes_with_schema— different schema → different pipeline_hashtest_pipeline_hash_merkle_chain— downstream hash commits to upstream hashestest_commutative_join_pipeline_hash_order_independent— join(A,B) pipeline_hash == join(B,A)test_non_commutative_semijoin_pipeline_hash_order_dependent— semijoin(A,B) != semijoin(B,A)
test_source_creates_system_tag_column— source adds _tag::source:hash columntest_unary_operator_preserves_system_tags— filter/select/map: name+value unchangedtest_join_extends_system_tag_names— multi-input: column names get ::hash:pos suffixtest_join_sorts_system_tag_values— commutative ops sort tag valuestest_batch_evolves_system_tag_type— batch: str → list[str]test_full_pipeline_provenance_chain— source → join → filter → batch: all rules applied
test_datagram_column_config_meta— meta=True includes __ columnstest_datagram_column_config_data_only— all False = data columns onlytest_tag_column_config_system_tags— system_tags=True includes _tag:: columnstest_data_column_config_source— source=True includes source columnstest_stream_column_config_all_info— all_info=True on keys/output_schema/as_tabletest_stream_column_config_consistency— keys(), output_schema(), as_table() all respect same config
test_schema_merge_commutative— merge(A,B) == merge(B,A) when compatibletest_schema_select_then_drop_complementary— select(X) ∪ drop(X) == originaltest_schema_is_compatible_reflexive— A.is_compatible_with(A) always Truetest_schema_optional_fields_subset_of_all_fields
test_hash_deterministic— hash(X) == hash(X) for any Xtest_hash_changes_with_any_field_mutation— mutate one value → different hashtest_content_hash_string_roundtrip— from_string(to_string(h)) == h for any h
test_join_commutativity— join(A,B) data == join(B,A) datatest_join_associativity— join(join(A,B),C) data == join(A,join(B,C)) datatest_filter_idempotency— filter(filter(S, P), P) == filter(S, P)test_select_then_select_is_intersection— select(select(S, X), Y) == select(S, X∩Y)test_drop_then_drop_is_union— drop(drop(S, X), Y) == drop(S, X∪Y)
- Property-based testing (Hypothesis) — generate random schemas, data, operations and verify algebraic invariants hold
- Algebraic property testing — verify mathematical properties (commutativity of join, idempotency of filter, etc.)
- Mutation testing with
mutmut— runuv run mutmut run --paths-to-mutate=src/orcapod/ --tests-dir=test-objective/to verify tests catch code mutations. A surviving mutant indicates a test gap - Metamorphic testing — "if I add a row to source A that matches source B's tags, the join output should have one more row" — tests relationships between inputs/outputs without knowing exact expected values
- Protocol conformance automation — use
runtime_checkableprotocols andisinstancechecks to verify every concrete class satisfies its protocol at import time - Specification oracle — for each documented behavior in
orcapod-design.md, create a test that constructs the exact scenario described and verifies the documented outcome - Fuzz testing — feed malformed inputs (wrong types, extreme sizes, Unicode edge cases) to constructors and verify graceful error handling
conftest.py— shared fixtures (reusable sources, streams, data functions, databases)unit/test_types.py— foundational types (Schema, ContentHash, ColumnConfig)unit/test_datagram.py,test_tag.py,test_data.py— data containersunit/test_stream.py— stream construction and iterationunit/test_sources.py+test_source_registry.py— all source typesunit/test_hashing.py— semantic hasher and handlersunit/test_schema_utils.py+test_arrow_utils.py+test_arrow_data_utils.py— utilitiesunit/test_semantic_types.py+test_contexts.py— type conversion and contextsunit/test_databases.py— database implementationsunit/test_data_function.py— data function behaviorunit/test_function_pod.py— function pod and streamsunit/test_operators.py— all operatorsunit/test_nodes.py— function/operator nodesunit/test_tracker.py+test_lazy_module.py— remaining unitsintegration/— all integration test filesproperty/— property-based tests
- hypothesis — added as a test dependency for property-based testing in
test-objective/property/ - pytest — test runner (already present)
- DeltaTableDatabase tests marked with
@pytest.mark.slow(skip with-m "not slow")
Run the full test suite with:
uv run pytest test-objective/ -vRun only unit tests:
uv run pytest test-objective/unit/ -vRun only integration tests:
uv run pytest test-objective/integration/ -vRun only property tests:
uv run pytest test-objective/property/ -v- New:
TESTING_PLAN.md(project root) — the test case catalog document (content mirrors this plan) - New:
test-objective/directory tree — all files listed in the structure above - No modifications to any existing source code or tests