diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 83fa32fb..a463a491 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -20,47 +20,42 @@ jobs: steps: - name: Clone repo uses: actions/checkout@v3 - - name: Setup Python - uses: actions/setup-python@v4 - with: - python-version: '3.11' - - uses: actions/cache@v3 + + # install & configure poetry + - name: Cache Poetry Installation + id: cached-poetry + uses: actions/cache@v3 with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-3.11 - restore-keys: ${{ runner.os }}-pip-3.11 - #---------------------------------------------- - # ----- install & configure poetry ----- - #---------------------------------------------- + path: ~/.local + key: ${{ runner.os }}-poetry-${{ github.event.pull_request.id || github.event.after }} - name: Install Poetry uses: snok/install-poetry@v1 + if: steps.cached-poetry.outputs.cache-hit != 'true' with: virtualenvs-create: true virtualenvs-in-project: true - #---------------------------------------------- - # load cached venv if cache exists - #---------------------------------------------- - - name: Load cached venv - id: cached-poetry-dependencies + + - name: Setup Python + id: setup-python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + cache: 'poetry' + - name: Store Pip Cache uses: actions/cache@v3 with: - path: .venv - key: venv-${{ runner.os }}-${{ hashFiles('**/poetry.lock') }}-3.11 - #---------------------------------------------- - # install dependencies if cache does not exist - #---------------------------------------------- + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ steps.setup-python.outputs.python-version }} + restore-keys: ${{ runner.os }}-pip-${{ steps.setup-python.outputs.python-version }} + + # install dependencies - name: Install dependencies - if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' + if: steps.setup-python.outputs.cache-hit != 'true' run: poetry install --no-interaction --no-ansi --no-root - #---------------------------------------------- - # install your root project, if required - #---------------------------------------------- - name: Install library run: poetry install --no-interaction --no-ansi - #---------------------------------------------- - # run python style checks - #---------------------------------------------- + # run python style checks - name: Poetry Lock Check run: poetry check --lock - name: isort @@ -72,7 +67,7 @@ jobs: - name: ruff run: poetry run ruff snakebids - test: + build-cache-env: runs-on: ubuntu-latest needs: [ 'quality' ] strategy: @@ -80,49 +75,94 @@ jobs: python-version: ['3.8', '3.9', '3.10', '3.11'] steps: - uses: actions/checkout@v3 + + # install & configure poetry + - name: Cache Poetry Installation + id: cached-poetry + uses: actions/cache@v3 + with: + path: ~/.local + key: ${{ runner.os }}-poetry-${{ github.event.pull_request.id || github.event.after }} + - name: Install Poetry + if: steps.cached-poetry.outputs.cache-hit != 'true' + uses: snok/install-poetry@v1 + with: + virtualenvs-create: true + virtualenvs-in-project: true + - name: Set up Python ${{ matrix.python-version }} + id: setup-python uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - - - uses: actions/cache@v3 + cache: 'poetry' + - name: Store pip cache + uses: actions/cache@v3 with: path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ matrix.python-version }} - restore-keys: ${{ runner.os }}-pip-${{ matrix.python-version }} - #---------------------------------------------- - # ----- install & configure poetry ----- - #---------------------------------------------- + key: ${{ runner.os }}-pip-${{ steps.setup-python.outputs.python-version }} + restore-keys: | + ${{ runner.os }}-pip-${{ steps.setup-python.outputs.python-version }} + ${{ runner.os }}-pip- + + # install dependencies if cache does not exist + - name: Install dependencies + if: steps.setup-python.outputs.cache-hit != 'true' + run: poetry install --no-interaction --no-root --no-ansi + + test: + runs-on: ubuntu-latest + needs: [ 'build-cache-env' ] + strategy: + matrix: + python-version: ['3.8', '3.9', '3.10', '3.11'] + split: ['1', '2', '3', '4', '5'] + fail-fast: false + steps: + - uses: actions/checkout@v3 + + # install & configure poetry + - name: Cache Poetry Installation + id: cached-poetry + uses: actions/cache@v3 + with: + path: ~/.local + key: ${{ runner.os }}-poetry-${{ github.event.pull_request.id || github.event.after }} - name: Install Poetry uses: snok/install-poetry@v1 + if: steps.cached-poetry.outputs.cache-hit != 'true' with: virtualenvs-create: true virtualenvs-in-project: true - #---------------------------------------------- - # load cached venv if cache exists - #---------------------------------------------- - - name: Load cached venv - id: cached-poetry-dependencies - uses: actions/cache@v3 + + - name: Set up Python ${{ matrix.python-version }} + id: setup-python + uses: actions/setup-python@v4 with: - path: .venv - key: venv-${{ runner.os }}-${{ hashFiles('**/poetry.lock') }}-${{ matrix.python-version }} - #---------------------------------------------- - # install dependencies if cache does not exist - #---------------------------------------------- + python-version: ${{ matrix.python-version }} + cache: poetry + - uses: actions/cache@v3 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ steps.setup-python.outputs.python-version }} + restore-keys: | + ${{ runner.os }}-pip-${{ steps.setup-python.outputs.python-version }} + ${{ runner.os }}-pip- + + # install dependencies - name: Install dependencies - if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' + if: steps.setup-python.outputs.cache-hit != 'true' run: poetry install --no-interaction --no-root --no-ansi - #---------------------------------------------- - # install your root project, if required - #---------------------------------------------- - name: Install library run: poetry install --no-interaction --no-ansi + - name: Test with pytest env: HYPOTHESIS_PROFILE: pr - run: | - poetry run pytest --doctest-modules --ignore=docs --ignore=snakebids/project_template --benchmark-disable + run: >- + poetry run pytest -n auto --splits 5 --group ${{ matrix.split }} + --doctest-modules --ignore=docs + --ignore=snakebids/project_template --benchmark-disable deployment_on_base: runs-on: ubuntu-latest diff --git a/.test_durations b/.test_durations new file mode 100644 index 00000000..8a98ea45 --- /dev/null +++ b/.test_durations @@ -0,0 +1,200 @@ +{ + "snakebids/tests/test_admin.py::TestAdminCli::test_boutiques_succeeds": 0.0015647239997633733, + "snakebids/tests/test_admin.py::TestAdminCli::test_create_succeeds": 0.001522717000625562, + "snakebids/tests/test_admin.py::TestAdminCli::test_fails_if_invalid_subcommand": 0.0014955139995436184, + "snakebids/tests/test_admin.py::TestAdminCli::test_fails_if_no_subcommand": 0.002441448000354285, + "snakebids/tests/test_app.py::TestGenBoutiques::test_boutiques_descriptor": 0.004023776000394719, + "snakebids/tests/test_app.py::TestRunSnakemake::test_get_app_version_no_package": 0.0024552509985369397, + "snakebids/tests/test_app.py::TestRunSnakemake::test_get_app_version_package": 0.003638418999798887, + "snakebids/tests/test_app.py::TestRunSnakemake::test_plugin_args": 0.008125161999487318, + "snakebids/tests/test_app.py::TestRunSnakemake::test_plugins": 0.007193429999460932, + "snakebids/tests/test_app.py::TestRunSnakemake::test_pybidsdb_path_resolved": 0.007840721999855305, + "snakebids/tests/test_app.py::TestRunSnakemake::test_runs_in_correct_mode": 1.209062509999967, + "snakebids/tests/test_app.py::TestUpdateConfig::test_magic_args": 1.9807073669999227, + "snakebids/tests/test_app.py::TestUpdateConfig::test_magic_optional_filter": 4.38338571300028, + "snakebids/tests/test_cli.py::TestAddDynamicArgs::test_converts_type_path_into_pathlike": 0.0012509789994510356, + "snakebids/tests/test_cli.py::TestAddDynamicArgs::test_dynamic_inputs": 4.1436772939996445, + "snakebids/tests/test_cli.py::TestAddDynamicArgs::test_fails_if_missing_arguments": 0.0017907569999806583, + "snakebids/tests/test_cli.py::TestAddDynamicArgs::test_fails_if_undefined_type_given": 0.0008545220007363241, + "snakebids/tests/test_cli.py::TestAddDynamicArgs::test_none_filters": 3.1218875509994177, + "snakebids/tests/test_cli.py::TestAddDynamicArgs::test_optional_filters": 2.8542928840006425, + "snakebids/tests/test_cli.py::TestAddDynamicArgs::test_required_filters": 3.946335863999593, + "snakebids/tests/test_cli.py::TestAddDynamicArgs::test_resolves_paths": 0.001395799999954761, + "snakebids/tests/test_cli.py::TestAddDynamicArgs::test_succeeds_if_given_positional_args": 0.001279684001019632, + "snakebids/tests/test_cli.py::TestResolvePath::test_does_not_change_dict_without_paths": 0.0005607799994322704, + "snakebids/tests/test_cli.py::TestResolvePath::test_filter_dict": 0.0005654810001942678, + "snakebids/tests/test_cli.py::TestResolvePath::test_resolves_all_paths": 0.0007531089995609364, + "snakebids/tests/test_cli.py::test_dash_syntax_in_config_cli_args": 0.0015748249998068786, + "snakebids/tests/test_datasets.py::TestBidsComponentAliases::test_bids_component_aliases_are_correctly_set": 3.585065563000171, + "snakebids/tests/test_datasets.py::TestBidsComponentAliases::test_bids_dataset_aliases_are_correctly_set": 4.607701994999843, + "snakebids/tests/test_datasets.py::TestBidsComponentEq::test_copies_are_equal": 4.0703872690000935, + "snakebids/tests/test_datasets.py::TestBidsComponentEq::test_empty_BidsInput_are_equal": 0.00060508599926834, + "snakebids/tests/test_datasets.py::TestBidsComponentEq::test_extra_entities_makes_unequal": 6.028903563999847, + "snakebids/tests/test_datasets.py::TestBidsComponentEq::test_mutation_makes_unequal": 5.820107650999489, + "snakebids/tests/test_datasets.py::TestBidsComponentEq::test_order_doesnt_affect_equality": 5.59472612799982, + "snakebids/tests/test_datasets.py::TestBidsComponentEq::test_other_types_are_unequal": 4.327248975000657, + "snakebids/tests/test_datasets.py::TestBidsComponentEq::test_paths_must_be_identical": 3.898123308000322, + "snakebids/tests/test_datasets.py::TestBidsComponentExpand::test_expand_with_no_args_returns_initial_paths": 6.234262529999796, + "snakebids/tests/test_datasets.py::TestBidsComponentExpand::test_not_expand_over_internal_path_when_novel_given": 5.527677684999617, + "snakebids/tests/test_datasets.py::TestBidsComponentIndexing::test_all_requested_items_received_and_no_others": 5.666300365000097, + "snakebids/tests/test_datasets.py::TestBidsComponentIndexing::test_multiple_missing_key_raises_error": 6.689564611001515, + "snakebids/tests/test_datasets.py::TestBidsComponentIndexing::test_multiple_selection_returns_BidsPartialComponent": 4.894498756000758, + "snakebids/tests/test_datasets.py::TestBidsComponentIndexing::test_order_of_selectors_is_preserved": 4.502908108999691, + "snakebids/tests/test_datasets.py::TestBidsComponentIndexing::test_selected_items_in_original": 6.173588208000183, + "snakebids/tests/test_datasets.py::TestBidsComponentIndexing::test_single_missing_key_raises_error": 6.944297463998737, + "snakebids/tests/test_datasets.py::TestBidsComponentIndexing::test_single_selection_returns_BidsComponentRow": 4.878274323000369, + "snakebids/tests/test_datasets.py::TestBidsComponentProperties::test_input_lists_derives_from_zip_lists": 2.6722491609989447, + "snakebids/tests/test_datasets.py::TestBidsComponentProperties::test_input_wildcards_derives_from_zip_lists": 0.567857134001315, + "snakebids/tests/test_datasets.py::TestBidsComponentValidation::test_path_cannot_have_extra_entities": 2.5555411189989172, + "snakebids/tests/test_datasets.py::TestBidsComponentValidation::test_path_cannot_have_missing_entities": 3.534925416999613, + "snakebids/tests/test_datasets.py::TestBidsComponentValidation::test_zip_lists_must_be_same_length": 3.5091521859994828, + "snakebids/tests/test_datasets.py::TestBidsDatasetLegacyAccess::test_accessing_legacy_names_leads_to_informative_error": 17.540724161999606, + "snakebids/tests/test_datasets.py::TestBidsDatasetLegacyAccess::test_accessing_ordinary_missing_names_leads_to_regular_error": 9.727966432999892, + "snakebids/tests/test_datasets.py::TestBidsDatasetLegacyAccess::test_legacy_names_can_be_accessed_if_component_name": 5.696896455000569, + "snakebids/tests/test_datasets.py::TestExpandables::test_expand_deduplicates_paths": 7.500194562000615, + "snakebids/tests/test_datasets.py::TestExpandables::test_expand_over_multiple_paths": 4.444507289000285, + "snakebids/tests/test_datasets.py::TestExpandables::test_expand_with_extra_args_returns_all_paths": 11.22675696999977, + "snakebids/tests/test_datasets.py::TestExpandables::test_partial_expansion": 8.347661129000699, + "snakebids/tests/test_datasets.py::TestExpandables::test_prevent_partial_expansion": 7.544019992999893, + "snakebids/tests/test_datasets.py::TestFiltering::test_all_columns_found_in_original_zip_list": 4.8862109629999395, + "snakebids/tests/test_datasets.py::TestFiltering::test_all_entities_remain_after_filtering": 6.365838602000622, + "snakebids/tests/test_datasets.py::TestFiltering::test_no_columns_that_should_be_present_are_missing": 4.458120583000891, + "snakebids/tests/test_datasets.py::TestFiltering::test_only_filter_values_in_output": 5.26837103500111, + "snakebids/tests/test_datasets.py::TestFiltering::test_zip_lists_rows_remain_of_equal_length": 4.467788828999801, + "snakebids/tests/test_datasets.py::TestFilteringBidsComponentRowWithSpec::test_all_columns_found_in_original_zip_list": 1.1291200990008292, + "snakebids/tests/test_datasets.py::TestFilteringBidsComponentRowWithSpec::test_all_entities_remain_after_filtering": 1.0580987409985028, + "snakebids/tests/test_datasets.py::TestFilteringBidsComponentRowWithSpec::test_all_valid_values_in_spec_in_result": 1.248238534999473, + "snakebids/tests/test_datasets.py::TestFilteringBidsComponentRowWithSpec::test_only_filter_values_in_output": 1.0173590180002066, + "snakebids/tests/test_datasets.py::TestFilteringBidsComponentRowWithSpec::test_providing_both_spec_and_filters_gives_error": 1.288486692999868, + "snakebids/tests/test_datasets.py::test_multiple_components_cannot_have_same_name": 0.000496370001201285, + "snakebids/tests/test_filtering.py::test_filter_list[zip_list0-filters0-output0]": 0.0007655110002815491, + "snakebids/tests/test_filtering.py::test_filter_list[zip_list1-filters1-output1]": 0.000646290999611665, + "snakebids/tests/test_filtering.py::test_filter_list[zip_list2-filters2-output2]": 0.0006152880005174666, + "snakebids/tests/test_filtering.py::test_filter_list[zip_list3-filters3-output3]": 0.00061978900066606, + "snakebids/tests/test_filtering.py::test_filter_list[zip_list4-filters4-output4]": 0.00061268700119399, + "snakebids/tests/test_filtering.py::test_filter_list[zip_list5-filters5-output5]": 0.0006315899991022889, + "snakebids/tests/test_generate_inputs.py::TestAbsentConfigEntries::test_missing_filters": 0.05270813900006033, + "snakebids/tests/test_generate_inputs.py::TestAbsentConfigEntries::test_missing_wildcards": 0.056271448000188684, + "snakebids/tests/test_generate_inputs.py::TestCustomPaths::test_benchmark_test_custom_paths": 1.2245839139986856, + "snakebids/tests/test_generate_inputs.py::TestCustomPaths::test_collect_all_but_filters_when_exclusion_filters_used": 1.8162020430008852, + "snakebids/tests/test_generate_inputs.py::TestCustomPaths::test_collects_all_paths_when_no_filters": 1.3868155139998635, + "snakebids/tests/test_generate_inputs.py::TestCustomPaths::test_collects_only_filtered_entities": 1.1606466719995296, + "snakebids/tests/test_generate_inputs.py::TestDB::test_pybidsdb_dir_absolute": 0.4496726010002021, + "snakebids/tests/test_generate_inputs.py::TestDB::test_pybidsdb_dir_blank": 0.05659259200001543, + "snakebids/tests/test_generate_inputs.py::TestDB::test_pybidsdb_dir_relative": 0.054021924000153376, + "snakebids/tests/test_generate_inputs.py::TestFilterBools::test_ambiguous_paths_with_extra_entities_leads_to_error": 19.18962320600076, + "snakebids/tests/test_generate_inputs.py::TestFilterBools::test_ambiguous_paths_with_missing_entity_leads_to_error": 22.35742768, + "snakebids/tests/test_generate_inputs.py::TestFilterBools::test_entity_excluded_when_filter_false": 14.066539062998345, + "snakebids/tests/test_generate_inputs.py::TestFilterBools::test_entity_excluded_when_filter_true": 19.639874497000164, + "snakebids/tests/test_generate_inputs.py::TestFilterBools::test_filter_blank_paths_when_false_in_list": 17.268236902999888, + "snakebids/tests/test_generate_inputs.py::TestFilterBools::test_filter_works_when_false_in_list": 17.393551884999397, + "snakebids/tests/test_generate_inputs.py::TestGenBidsLayout::test_gen_layout_returns_valid_dataset": 0.26430688399977953, + "snakebids/tests/test_generate_inputs.py::TestGenBidsLayout::test_invalid_path_raises_error": 0.0063425079997614375, + "snakebids/tests/test_generate_inputs.py::TestGenerateFilter::test_exclude_gives_regex_that_matches_anything_except_exclude": 0.7182105030005914, + "snakebids/tests/test_generate_inputs.py::TestGenerateFilter::test_returns_participant_label_as_list": 0.33483348099889554, + "snakebids/tests/test_generate_inputs.py::TestGenerateFilter::test_throws_error_if_labels_and_excludes_are_given": 0.46052325400160044, + "snakebids/tests/test_generate_inputs.py::TestParseBidsPath::test_missing_match_leads_to_error": 4.518304296999304, + "snakebids/tests/test_generate_inputs.py::TestParseBidsPath::test_one_match_found_for_each_entity": 8.72459741700004, + "snakebids/tests/test_generate_inputs.py::TestParseBidsPath::test_splits_wildcards_from_path": 7.7673003710006014, + "snakebids/tests/test_generate_inputs.py::test_all_custom_paths[0]": 0.0005130720001034206, + "snakebids/tests/test_generate_inputs.py::test_all_custom_paths[1]": 0.0005125739999130019, + "snakebids/tests/test_generate_inputs.py::test_all_custom_paths[2]": 0.0004889700003332109, + "snakebids/tests/test_generate_inputs.py::test_all_custom_paths[3]": 0.0004895700003544334, + "snakebids/tests/test_generate_inputs.py::test_all_custom_paths[4]": 0.00048496899853489595, + "snakebids/tests/test_generate_inputs.py::test_all_custom_paths[5]": 0.00048486799914826406, + "snakebids/tests/test_generate_inputs.py::test_custom_pybids_config": 0.05877560200042353, + "snakebids/tests/test_generate_inputs.py::test_generate_inputs": 21.602900685998975, + "snakebids/tests/test_generate_inputs.py::test_get_lists_from_bids": 0.08029437799905281, + "snakebids/tests/test_generate_inputs.py::test_get_lists_from_bids_raises_pybids_error": 0.0003387480001038057, + "snakebids/tests/test_generate_inputs.py::test_get_lists_from_bids_raises_run_error": 0.0006311899996944703, + "snakebids/tests/test_generate_inputs.py::test_nonstandard_custom_pybids_config": 0.0562525410005037, + "snakebids/tests/test_generate_inputs.py::test_t1w": 0.24037436399976286, + "snakebids/tests/test_generate_inputs.py::test_t1w_with_dict": 0.11780284200085589, + "snakebids/tests/test_generate_inputs.py::test_when_all_custom_paths_no_layout_indexed": 0.18553174199951172, + "snakebids/tests/test_output.py::TestGetSnakebidsFile::test_raises_error_if_contents_but_no_snakebids_file": 0.0013753959992754972, + "snakebids/tests/test_output.py::TestGetSnakebidsFile::test_raises_error_if_malformed_snakebids_file_found": 0.0011450630008766893, + "snakebids/tests/test_output.py::TestGetSnakebidsFile::test_returns_contents_of_snakebids_file_if_valid": 0.0012528779998319806, + "snakebids/tests/test_output.py::TestGetSnakebidsFile::test_returns_none_if_directory_empty": 0.0010329480001018965, + "snakebids/tests/test_output.py::TestGetSnakebidsFile::test_returns_none_if_directory_nonexistant": 0.001008843999443343, + "snakebids/tests/test_output.py::TestPrepareBidsappOutput::test_creates_new_empty_bidsapp": 0.0027530939996722736, + "snakebids/tests/test_output.py::TestPrepareBidsappOutput::test_fails_when_directory_has_contents": 0.0023012290002952795, + "snakebids/tests/test_output.py::TestPrepareBidsappOutput::test_success_on_directory_with_contents_when_forced": 0.0028609089995370596, + "snakebids/tests/test_paths/test_bids.py::test_beginning_of_name_always_prefix": 1.567793565998727, + "snakebids/tests/test_paths/test_bids.py::test_beginning_of_path_always_root": 1.831682914999874, + "snakebids/tests/test_paths/test_bids.py::test_benchmark_bids": 0.4198331239995241, + "snakebids/tests/test_paths/test_bids.py::test_bids_with_no_args_gives_empty_path": 0.00042796200068551116, + "snakebids/tests/test_paths/test_bids.py::test_bottom_directory_always_datatype": 2.0822916439992696, + "snakebids/tests/test_paths/test_bids.py::test_datatype_not_in_path_name": 1.614058890000706, + "snakebids/tests/test_paths/test_bids.py::test_dir_entities_each_own_dir": 1.116841174000001, + "snakebids/tests/test_paths/test_bids.py::test_directories_in_correct_order": 0.8718303449995801, + "snakebids/tests/test_paths/test_bids.py::test_end_of_path_always_suffix_extension": 2.2432539530000213, + "snakebids/tests/test_paths/test_bids.py::test_entities_all_in_path_as_tags": 1.657014531000641, + "snakebids/tests/test_paths/test_bids.py::test_entities_found_in_name_in_correct_order": 3.1585444169995753, + "snakebids/tests/test_paths/test_bids.py::test_full_entity_names_not_in_path": 0.48001803700117307, + "snakebids/tests/test_paths/test_bids.py::test_long_and_short_names_cannot_be_used_simultaneously": 0.48433865399965725, + "snakebids/tests/test_paths/test_bids.py::test_missing_essential_entities_gives_error": 0.219051018000755, + "snakebids/tests/test_paths/test_bids.py::test_no_underscore_at_end_if_no_suffix": 1.4327360689994748, + "snakebids/tests/test_paths/test_bids.py::test_nondir_entities_dont_have_dirs": 2.1030832759997793, + "snakebids/tests/test_paths/test_bids.py::test_number_of_dashes_corresponds_to_number_entities": 1.121891880999101, + "snakebids/tests/test_paths/test_bids.py::test_number_of_underscore_corresponds_to_number_entities": 1.5914777530006177, + "snakebids/tests/test_paths/test_bids.py::test_underscore_does_not_precede_extension": 1.6791530060008881, + "snakebids/tests/test_paths/test_bids.py::test_underscore_precedes_suffix": 1.7751927379995323, + "snakebids/tests/test_paths/test_bids.py::test_values_paired_with_entities": 2.000123156999507, + "snakebids/tests/test_paths/test_specs.py::test_all_entries_define_entity": 0.003393484999833163, + "snakebids/tests/test_paths/test_specs.py::test_session_dir_can_be_excluded": 0.004217202999825531, + "snakebids/tests/test_paths/test_specs.py::test_subject_dir_can_be_excluded": 0.003142449999359087, + "snakebids/tests/test_printing.py::TestCorrectNumberOfLinesCreated::test_in_component": 9.629228948999298, + "snakebids/tests/test_printing.py::TestCorrectNumberOfLinesCreated::test_in_dataset": 13.436842013999922, + "snakebids/tests/test_printing.py::TestCorrectNumberOfLinesCreated::test_in_zip_list": 3.884400208000443, + "snakebids/tests/test_printing.py::TestIndentLengthMultipleOfTabStop::test_in_component": 9.769521872999576, + "snakebids/tests/test_printing.py::TestIndentLengthMultipleOfTabStop::test_in_dataset": 17.117199166998944, + "snakebids/tests/test_printing.py::TestIndentLengthMultipleOfTabStop::test_in_zip_list": 4.451645713999824, + "snakebids/tests/test_printing.py::TestIsValidPython::test_in_component": 8.59832459899826, + "snakebids/tests/test_printing.py::TestIsValidPython::test_in_dataset": 19.468040342999302, + "snakebids/tests/test_printing.py::TestIsValidPython::test_in_zip_list": 6.283397254999727, + "snakebids/tests/test_printing.py::TestMultipleLevelsOfIndentationUsed::test_in_component": 10.59685330999946, + "snakebids/tests/test_printing.py::TestMultipleLevelsOfIndentationUsed::test_in_dataset": 17.170289044999663, + "snakebids/tests/test_printing.py::test_ellipses_appears_when_maxwidth_too_short": 1.6554146170001331, + "snakebids/tests/test_printing.py::test_line_never_longer_than_max_width": 3.1382278920000317, + "snakebids/tests/test_printing.py::test_values_balanced_around_elision_correctly": 3.8712674559992593, + "snakebids/tests/test_snakemake_io.py::test_glob_wildcards": 0.001319388999036164, + "snakebids/tests/test_template.py::test_template_dry_runs_successfully": 0.7241960929995912, + "snakebids/tests/test_utils.py::TestImmutableListsAreEquivalentToTuples::test_bool": 1.8436534800002846, + "snakebids/tests/test_utils.py::TestImmutableListsAreEquivalentToTuples::test_cannot_be_mutated": 0.9749702349990912, + "snakebids/tests/test_utils.py::TestImmutableListsAreEquivalentToTuples::test_contains_items": 0.859012729000824, + "snakebids/tests/test_utils.py::TestImmutableListsAreEquivalentToTuples::test_equal_items_are_equal": 1.0922983569998905, + "snakebids/tests/test_utils.py::TestImmutableListsAreEquivalentToTuples::test_has_length": 1.0503948940004193, + "snakebids/tests/test_utils.py::TestImmutableListsAreEquivalentToTuples::test_is_hashable": 1.0240762319999703, + "snakebids/tests/test_utils.py::TestImmutableListsAreEquivalentToTuples::test_is_iterable": 1.4604384260001098, + "snakebids/tests/test_utils.py::TestImmutableListsAreEquivalentToTuples::test_is_reversable": 0.869134975999259, + "snakebids/tests/test_utils.py::TestImmutableListsAreEquivalentToTuples::test_repr": 0.4380282000001898, + "snakebids/tests/test_utils.py::TestImmutableListsAreEquivalentToTuples::test_supports_addition": 0.6370974809988184, + "snakebids/tests/test_utils.py::TestImmutableListsAreEquivalentToTuples::test_supports_comparisons": 0.2215375710002263, + "snakebids/tests/test_utils.py::TestImmutableListsAreEquivalentToTuples::test_supports_count": 3.221306003000791, + "snakebids/tests/test_utils.py::TestImmutableListsAreEquivalentToTuples::test_supports_equality": 1.5537622290012223, + "snakebids/tests/test_utils.py::TestImmutableListsAreEquivalentToTuples::test_supports_getting": 4.248907180999595, + "snakebids/tests/test_utils.py::TestImmutableListsAreEquivalentToTuples::test_supports_index": 3.0816749079995134, + "snakebids/tests/test_utils.py::TestImmutableListsAreEquivalentToTuples::test_supports_multiplication": 0.6437870369982193, + "snakebids/tests/test_utils.py::TestImmutableListsAreEquivalentToTuples::test_supports_slicing": 0.6651558920002572, + "snakebids/tests/test_utils.py::TestImmutableListsAreEquivalentToTuples::test_they_are_not_tuples": 3.090197783999429, + "snakebids/tests/test_utils.py::TestMatchesAny::test_with_email": 1.1653163060000225, + "snakebids/tests/test_utils.py::TestMatchesAny::test_with_eq_operator": 0.4310189959996933, + "snakebids/tests/test_utils.py::TestMatchesAny::test_with_re_match": 0.504306515999815, + "snakebids/tests/test_utils.py::TestMultiselectDict::test_all_requested_items_received_and_no_others": 3.2263401339996562, + "snakebids/tests/test_utils.py::TestMultiselectDict::test_multiple_missing_key_raises_error": 3.7488097069990545, + "snakebids/tests/test_utils.py::TestMultiselectDict::test_multiple_selection_returns_same_type": 2.652810520001367, + "snakebids/tests/test_utils.py::TestMultiselectDict::test_order_of_selectors_is_preserved": 3.208085850000316, + "snakebids/tests/test_utils.py::TestMultiselectDict::test_selected_items_in_original": 3.0269306469990624, + "snakebids/tests/test_utils.py::TestMultiselectDict::test_single_key_gives_single_item": 1.8614134979998198, + "snakebids/tests/test_utils.py::TestMultiselectDict::test_single_missing_key_raises_error": 2.4325211349996607, + "snakebids/tests/test_utils.py::TestRegexContainer::test_bytes_matching_regex_are_contained": 0.17801416400016024, + "snakebids/tests/test_utils.py::TestRegexContainer::test_container_specific_to_type": 0.1314744060000521, + "snakebids/tests/test_utils.py::TestRegexContainer::test_str_matching_regex_are_contained": 0.20552259999931266, + "snakebids/tests/test_utils.py::TestRegexContainer::test_str_not_matching_regex_excluded": 0.1824607990001823, + "snakebids/tests/test_utils.py::test_get_wildcard_dict": 0.6484821629992439, + "snakebids/tests/test_validate_plugin.py::TestBidsValidator::test_ignore_validation_error": 0.002800000000206637, + "snakebids/tests/test_validate_plugin.py::TestBidsValidator::test_missing_bids_validator": 0.0030920420003894833, + "snakebids/tests/test_validate_plugin.py::TestBidsValidator::test_raise_validation_error": 0.0026461770003152196, + "snakebids/tests/test_validate_plugin.py::TestBidsValidator::test_skip_validation": 0.0019650819995149504, + "snakebids/tests/test_validate_plugin.py::TestBidsValidator::test_validation_successful": 0.0032324629992217524 +} diff --git a/poetry.lock b/poetry.lock index e6023b80..f03ed824 100644 --- a/poetry.lock +++ b/poetry.lock @@ -448,6 +448,20 @@ files = [ [package.extras] test = ["pytest (>=6)"] +[[package]] +name = "execnet" +version = "2.0.2" +description = "execnet: rapid multi-Python deployment" +optional = false +python-versions = ">=3.7" +files = [ + {file = "execnet-2.0.2-py3-none-any.whl", hash = "sha256:88256416ae766bc9e8895c76a87928c0012183da3cc4fc18016e6f050e025f41"}, + {file = "execnet-2.0.2.tar.gz", hash = "sha256:cc59bc4423742fd71ad227122eb0dd44db51efb3dc4095b45ac9a08c770096af"}, +] + +[package.extras] +testing = ["hatch", "pre-commit", "pytest", "tox"] + [[package]] name = "fastjsonschema" version = "2.18.0" @@ -1814,6 +1828,40 @@ pytest = ">=5.0" [package.extras] dev = ["pre-commit", "pytest-asyncio", "tox"] +[[package]] +name = "pytest-split" +version = "0.8.1" +description = "Pytest plugin which splits the test suite to equally sized sub suites based on test execution time." +optional = false +python-versions = ">=3.7.1,<4.0" +files = [ + {file = "pytest_split-0.8.1-py3-none-any.whl", hash = "sha256:74b110ea091bd147cc1c5f9665a59506e5cedfa66f96a89fb03e4ab447c2c168"}, + {file = "pytest_split-0.8.1.tar.gz", hash = "sha256:2d88bd3dc528689a7a3f58fc12ea165c3aa62e90795e420dfad920afe5612d6d"}, +] + +[package.dependencies] +pytest = ">=5,<8" + +[[package]] +name = "pytest-xdist" +version = "3.3.1" +description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-xdist-3.3.1.tar.gz", hash = "sha256:d5ee0520eb1b7bcca50a60a518ab7a7707992812c578198f8b44fdfac78e8c93"}, + {file = "pytest_xdist-3.3.1-py3-none-any.whl", hash = "sha256:ff9daa7793569e6a68544850fd3927cd257cc03a7ef76c95e86915355e82b5f2"}, +] + +[package.dependencies] +execnet = ">=1.1" +pytest = ">=6.2.0" + +[package.extras] +psutil = ["psutil (>=3.0)"] +setproctitle = ["setproctitle"] +testing = ["filelock"] + [[package]] name = "python-dateutil" version = "2.8.2" @@ -2807,4 +2855,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = ">=3.8,<3.12" -content-hash = "d6bc4ca4c26ff0f36a6535221686ef48b8a064e2f1077dfe1fba51232a1b3e99" +content-hash = "48195b8d03dd2392cc9356c9784345bcd1e65105b6e8bc4c21a14db5045deab1" diff --git a/pyproject.toml b/pyproject.toml index 96f090b8..8bf4bd04 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -82,6 +82,8 @@ pyparsing = "^3.0.9" pathvalidate = "^3.0.0" pyright = ">=1.1.324" ruff = "^0.0.285" +pytest-xdist = "^3.3.1" +pytest-split = "^0.8.1" [tool.poetry.scripts] snakebids = "snakebids.admin:main" diff --git a/snakebids/tests/conftest.py b/snakebids/tests/conftest.py index d748890d..b9fcedc3 100644 --- a/snakebids/tests/conftest.py +++ b/snakebids/tests/conftest.py @@ -9,6 +9,7 @@ from hypothesis import settings from pyfakefs.fake_filesystem import FakeFilesystem +import snakebids.paths.resources as specs from snakebids import resources ## Hypothesis profiles @@ -66,4 +67,5 @@ def bids_fs(fakefs: FakeFilesystem | None) -> FakeFilesystem | None: fakefs.add_real_file(f / "bids.json") fakefs.add_real_file(f / "derivatives.json") fakefs.add_real_file(Path(*resources.__path__) / "bids_tags.json") + fakefs.add_real_directory(Path(*specs.__path__)) return fakefs diff --git a/snakebids/tests/test_printing.py b/snakebids/tests/test_printing.py index 3402f9e2..1df0b887 100644 --- a/snakebids/tests/test_printing.py +++ b/snakebids/tests/test_printing.py @@ -1,6 +1,8 @@ # ruff: noqa: PLR2004 from __future__ import annotations +from math import inf + import pyparsing as pp from hypothesis import assume, given from hypothesis import strategies as st @@ -91,15 +93,15 @@ def test_in_dataset(self, dataset: BidsDataset): class TestIsValidPython: @given(zip_list=sb_st.zip_lists(restrict_patterns=True)) def test_in_zip_list(self, zip_list: ZipList): - assert eval(format_zip_lists(zip_list)) == zip_list + assert eval(format_zip_lists(zip_list, inf)) == zip_list @given(component=sb_st.bids_components(restrict_patterns=True)) def test_in_component(self, component: BidsComponent): - assert eval(component.pformat()) == component + assert eval(component.pformat(inf)) == component @given(dataset=sb_st.datasets()) def test_in_dataset(self, dataset: BidsDataset): - assert eval(dataset.pformat()) == dataset + assert eval(dataset.pformat(inf)) == dataset # this could also be tested for components and datasets, however, in those objects the diff --git a/typings/pyfakefs/fake_filesystem.pyi b/typings/pyfakefs/fake_filesystem.pyi index 1d406fea..162b3a0e 100644 --- a/typings/pyfakefs/fake_filesystem.pyi +++ b/typings/pyfakefs/fake_filesystem.pyi @@ -2,10 +2,9 @@ This type stub file was generated by pyright. """ -import os import sys from enum import Enum -from typing import Any, AnyStr, Callable, Dict, List, NoReturn, Optional, Tuple, Union +from typing import Any, AnyStr, Callable, NoReturn from pyfakefs.fake_file import AnyFile, AnyFileWrapper, FakeFile from pyfakefs.helpers import AnyPath, AnyString @@ -132,7 +131,7 @@ class FakeFilesystem: def __init__( self, path_separator: str = ..., - total_size: Optional[int] = ..., + total_size: int | None = ..., patcher: Any = ..., ) -> None: """ @@ -186,7 +185,7 @@ class FakeFilesystem: """Set the simulated type of operating system underlying the fake file system.""" ... - def reset(self, total_size: Optional[int] = ...): # -> None: + def reset(self, total_size: int | None = ...): # -> None: """Remove all file system contents and reset the root.""" ... def pause(self) -> None: @@ -217,8 +216,8 @@ class FakeFilesystem: def raise_os_error( self, err_no: int, - filename: Optional[AnyString] = ..., - winerror: Optional[int] = ..., + filename: AnyString | None = ..., + winerror: int | None = ..., ) -> NoReturn: """Raises OSError. The error message is constructed from the given error code and shall @@ -240,8 +239,8 @@ class FakeFilesystem: """Return True if path starts with a path separator.""" ... def add_mount_point( - self, path: AnyStr, total_size: Optional[int] = ..., can_exist: bool = ... - ) -> Dict: + self, path: AnyStr, total_size: int | None = ..., can_exist: bool = ... + ) -> dict: """Add a new mount point for a filesystem device. The mount point gets a new unique device number. @@ -262,7 +261,7 @@ class FakeFilesystem: and `can_exist` is False. """ ... - def get_disk_usage(self, path: Optional[AnyStr] = ...) -> Tuple[int, int, int]: + def get_disk_usage(self, path: AnyStr | None = ...) -> tuple[int, int, int]: """Return the total, used and free disk space in bytes as named tuple, or placeholder values simulating unlimited space if not set. @@ -274,7 +273,7 @@ class FakeFilesystem: Defaults to the root path (e.g. '/' on Unix systems). """ ... - def set_disk_usage(self, total_size: int, path: Optional[AnyStr] = ...) -> None: + def set_disk_usage(self, total_size: int, path: AnyStr | None = ...) -> None: """Changes the total size of the file system, preserving the used space. Example usage: set the size of an auto-mounted Windows drive. @@ -350,9 +349,9 @@ class FakeFilesystem: def utime( self, path: AnyStr, - times: Optional[Tuple[Union[int, float], Union[int, float]]] = ..., + times: tuple[int | float, int | float] | None = ..., *, - ns: Optional[Tuple[int, int]] = ..., + ns: tuple[int, int] | None = ..., follow_symlinks: bool = ... ) -> None: """Change the access and modified times of a file. @@ -449,7 +448,7 @@ class FakeFilesystem: or the root directory if path is empty. """ ... - def splitpath(self, path: AnyStr) -> Tuple[AnyStr, AnyStr]: + def splitpath(self, path: AnyStr) -> tuple[AnyStr, AnyStr]: """Mimic os.path.split using the specified path_separator. Mimics os.path.split using the path_separator that was specified @@ -463,7 +462,7 @@ class FakeFilesystem: end with a slash, and basename does not contain a slash. """ ... - def splitdrive(self, path: AnyStr) -> Tuple[AnyStr, AnyStr]: + def splitdrive(self, path: AnyStr) -> tuple[AnyStr, AnyStr]: """Splits the path into the drive part and the rest of the path. Taken from Windows specific implementation in Python 3.5 @@ -478,9 +477,7 @@ class FakeFilesystem: not supported or no drive is present. """ ... - def splitroot( - self, path: AnyStr - ): # -> tuple[bytes* | str*, Literal[b"", ''], Literal[b"", '']] | tuple[bytes* | str*, bytes* | str*, bytes* | str*] | tuple[Literal[b"", ''], bytes* | str*, bytes* | str*] | tuple[bytes* | str*, Literal[b"", ''], bytes* | str*] | tuple[Literal[b"", ''], Literal[b"", ''], bytes* | str*] | tuple[Literal[b"", ''], bytes | str, bytes* | str*]: + def splitroot(self, path: AnyStr): """Split a pathname into drive, root and tail. Implementation taken from ntpath and posixpath. """ @@ -516,7 +513,7 @@ class FakeFilesystem: def is_mount_point(self, file_path: AnyStr) -> bool: """Return `True` if `file_path` points to a mount point.""" ... - def ends_with_path_separator(self, path: Union[int, AnyPath]) -> bool: + def ends_with_path_separator(self, path: int | AnyPath) -> bool: """Return True if ``file_path`` ends with a valid path separator.""" ... def is_filepath_ending_with_separator(self, path: AnyStr) -> bool: ... @@ -730,12 +727,12 @@ class FakeFilesystem: file_path: AnyPath, st_mode: int = ..., contents: AnyString = ..., - st_size: Optional[int] = ..., + st_size: int | None = ..., create_missing_dirs: bool = ..., apply_umask: bool = ..., - encoding: Optional[str] = ..., - errors: Optional[str] = ..., - side_effect: Optional[Callable] = ..., + encoding: str | None = ..., + errors: str | None = ..., + side_effect: Callable | None = ..., ) -> FakeFile: """Create `file_path`, including all the parent directories along the way. @@ -769,9 +766,9 @@ class FakeFilesystem: ... def add_real_file( self, - source_path: AnyPath[str], + source_path: AnyPath[AnyStr], read_only: bool = ..., - target_path: Optional[AnyPath[str]] = ..., + target_path: AnyPath[AnyStr] | None = ..., ) -> FakeFile: """Create `file_path`, including all the parent directories along the way, for an existing real file. The contents of the real file are read @@ -799,7 +796,7 @@ class FakeFilesystem: """ ... def add_real_symlink( - self, source_path: AnyPath, target_path: Optional[AnyPath] = ... + self, source_path: AnyPath, target_path: AnyPath | None = ... ) -> FakeFile: """Create a symlink at source_path (or target_path, if given). It will point to the same path as the symlink on the real filesystem. Relative @@ -823,10 +820,10 @@ class FakeFilesystem: ... def add_real_directory( self, - source_path: AnyPath, + source_path: AnyPath[AnyStr], read_only: bool = ..., lazy_read: bool = ..., - target_path: Optional[AnyPath] = ..., + target_path: AnyPath[AnyStr] | None = ..., ) -> FakeDirectory: """Create a fake directory corresponding to the real directory at the specified path. Add entries in the fake directory corresponding to @@ -857,7 +854,7 @@ class FakeFilesystem: """ ... def add_real_paths( - self, path_list: List[AnyStr], read_only: bool = ..., lazy_dir_read: bool = ... + self, path_list: list[AnyStr], read_only: bool = ..., lazy_dir_read: bool = ... ) -> None: """This convenience method adds multiple files and/or directories from the real file system to the fake file system. See `add_real_file()` and @@ -885,13 +882,13 @@ class FakeFilesystem: file_path: AnyPath, st_mode: int = ..., contents: AnyString = ..., - st_size: Optional[int] = ..., + st_size: int | None = ..., create_missing_dirs: bool = ..., apply_umask: bool = ..., - encoding: Optional[str] = ..., - errors: Optional[str] = ..., + encoding: str | None = ..., + errors: str | None = ..., read_from_real_fs: bool = ..., - side_effect: Optional[Callable] = ..., + side_effect: Callable | None = ..., ) -> FakeFile: """Internal fake file creator that supports both normal fake files and fake files based on real files. @@ -1067,7 +1064,6 @@ class FakeFilesystem: TypeError: if path is None. """ ... - if sys.version_info >= (3, 12): ... def confirmdir( self, target_directory: AnyStr, check_owner: bool = ... ) -> FakeDirectory: @@ -1114,7 +1110,7 @@ class FakeFilesystem: Cannot remove '.'. """ ... - def listdir(self, target_directory: AnyStr) -> List[AnyStr]: + def listdir(self, target_directory: AnyStr) -> list[AnyStr]: """Return a list of file names in target_directory. Args: @@ -1131,5 +1127,3 @@ class FakeFilesystem: """ ... def __str__(self) -> str: ... - -if __name__ == "__main__": ...