diff --git a/source_modelling/gsf.py b/source_modelling/gsf.py index 7dcd260..ec44ed7 100644 --- a/source_modelling/gsf.py +++ b/source_modelling/gsf.py @@ -112,12 +112,15 @@ def write_gsf(gsf_df: pd.DataFrame, gsf_filepath: Path): The path to the GSF file to write. """ + if "loc_rake" not in gsf_df: + raise ValueError("The DataFrame must have a 'loc_rake' column.") + # Defaults are filled on a copy: write_gsf serialises the frame it is + # given and must not modify the caller's DataFrame. + gsf_df = gsf_df.copy(deep=False) if "init_time" not in gsf_df: gsf_df["init_time"] = -1 if "slip" not in gsf_df: gsf_df["slip"] = -1 - if "loc_rake" not in gsf_df: - raise ValueError("The DataFrame must have a 'loc_rake' column.") with open(gsf_filepath, "w") as gsf_file: gsf_file.write(f"{len(gsf_df)}\n") gsf_df.to_csv( diff --git a/tests/test_gsf.py b/tests/test_gsf.py index 864baa9..ad71091 100644 --- a/tests/test_gsf.py +++ b/tests/test_gsf.py @@ -359,3 +359,39 @@ def test_read_gsf(tmp_path: Path): ] ), ) + + +def test_write_gsf_does_not_mutate_input(tmp_path: Path): + """write_gsf must serialise its input without modifying it. + + Regression test: the ``init_time`` and ``slip`` defaults were assigned + directly into the caller's DataFrame, so a frame handed to ``write_gsf`` + came back carrying two columns of ``-1`` sentinels. The assignments also + sat above the ``loc_rake`` validation, so the mutation happened even when + ``write_gsf`` went on to raise and write nothing. + """ + gsf_df = pd.DataFrame( + { + "lon": [172.6], + "lat": [-43.5], + "dep": [1.0], + "sub_dx": [1.0], + "sub_dy": [1.0], + "loc_stk": [0.0], + "loc_dip": [0.0], + "loc_rake": [0.0], + "seg_no": [0], + } + ) + expected_columns = list(gsf_df.columns) + + gsf.write_gsf(gsf_df, tmp_path / "out.gsf") + + assert list(gsf_df.columns) == expected_columns + + # the failure path must not mutate either + missing_rake = gsf_df.drop(columns=["loc_rake"]) + expected_columns = list(missing_rake.columns) + with pytest.raises(ValueError, match="loc_rake"): + gsf.write_gsf(missing_rake, tmp_path / "out2.gsf") + assert list(missing_rake.columns) == expected_columns