diff --git a/codecarbon/cli/monitor.py b/codecarbon/cli/monitor.py index dbecf7855..3d6ea779d 100644 --- a/codecarbon/cli/monitor.py +++ b/codecarbon/cli/monitor.py @@ -120,13 +120,12 @@ def run_and_monitor( else: print(" Emissions: N/A") - # Show where the data was saved - if hasattr(tracker, "_conf") and "output_file" in tracker._conf: - output_path = tracker._conf["output_file"] - # Make it absolute if it's relative - if not os.path.isabs(output_path): - output_path = os.path.abspath(output_path) - print(f" Saved to: {output_path}") + # Show where the data was saved, asking the output handlers themselves + # so the path stays right whatever `output_dir` / `output_file` are. + for handler in getattr(tracker, "_output_handlers", []): + save_file_path = getattr(handler, "save_file_path", None) + if save_file_path: + print(f" Saved to: {os.path.abspath(save_file_path)}") print(" ⚠️ Note: Tracked the command process and its children") print("=" * 60) diff --git a/codecarbon/output_methods/boamps/output.py b/codecarbon/output_methods/boamps/output.py index ff172e9fa..61eac48f3 100644 --- a/codecarbon/output_methods/boamps/output.py +++ b/codecarbon/output_methods/boamps/output.py @@ -64,6 +64,8 @@ def __init__( self._quality = quality self._infra_overrides = infra_overrides self._environment_overrides = environment_overrides + # Path of the last report written, None until out() succeeds. + self.save_file_path: Optional[str] = None @classmethod def from_file(cls, context_file_path: str, output_dir: str = ".") -> "BoAmpsOutput": @@ -182,6 +184,7 @@ def out(self, total: EmissionsData, delta: EmissionsData): file_path = os.path.join(self._output_dir, file_name) with open(file_path, "w") as f: json.dump(report_dict, f, indent=2) + self.save_file_path = file_path logger.info(f"BoAmps report saved to {os.path.abspath(file_path)}") except Exception as e: logger.error(f"Failed to write BoAmps report: {e}", exc_info=True) diff --git a/tests/cli/test_monitor.py b/tests/cli/test_monitor.py index f3fa4852c..17ddf21aa 100644 --- a/tests/cli/test_monitor.py +++ b/tests/cli/test_monitor.py @@ -1,3 +1,4 @@ +import os from types import SimpleNamespace import pytest @@ -11,6 +12,7 @@ def __init__(self, *args, **kwargs): self.kwargs = kwargs self.stopped = 0 self._conf = {"output_file": "emissions.csv"} + self._output_handlers = [SimpleNamespace(save_file_path="emissions.csv")] def start(self): return None @@ -226,3 +228,50 @@ def kill(self): assert exc_info.value.exit_code == 130 assert process_info["terminated"] == 1 assert process_info["killed"] == 1 + + +def _run_and_capture(monkeypatch, capsys, handlers): + class FakePopen: + def __init__(self, command, text=True): + pass + + def wait(self): + return 0 + + class FakeTrackerWithHandlers(FakeTracker): + def __init__(self, **kwargs): + super().__init__(**kwargs) + self._output_handlers = handlers + + _patch_trackers(monkeypatch, online_cls=FakeTrackerWithHandlers) + monkeypatch.setattr(monitor_module.subprocess, "Popen", FakePopen) + + with pytest.raises(typer.Exit): + monitor_module.run_and_monitor(SimpleNamespace(args=["echo", "hi"])) + + # rich wraps long lines to the terminal width, so strip all whitespace + return "".join(capsys.readouterr().out.split()) + + +def test_run_and_monitor_reports_output_dir(monkeypatch, capsys, tmp_path): + save_file_path = os.path.join(str(tmp_path), "emissions.csv") + out = _run_and_capture( + monkeypatch, capsys, [SimpleNamespace(save_file_path=save_file_path)] + ) + + assert f"Savedto:{save_file_path}" in out + + +def test_run_and_monitor_reports_relative_path_from_cwd(monkeypatch, capsys, tmp_path): + monkeypatch.chdir(tmp_path) + out = _run_and_capture( + monkeypatch, capsys, [SimpleNamespace(save_file_path="emissions.csv")] + ) + + assert f"Savedto:{os.path.join(os.getcwd(), 'emissions.csv')}" in out + + +def test_run_and_monitor_reports_no_path_without_file_output(monkeypatch, capsys): + out = _run_and_capture(monkeypatch, capsys, [SimpleNamespace()]) + + assert "Savedto:" not in out diff --git a/tests/test_boamps_output.py b/tests/test_boamps_output.py index 06e46c13f..bf938f41e 100644 --- a/tests/test_boamps_output.py +++ b/tests/test_boamps_output.py @@ -539,6 +539,7 @@ def test_out_writes_json_file(self): self.tmpdir, f"boamps_report_{self.emissions.run_id}.json" ) self.assertTrue(os.path.isfile(expected_file)) + self.assertEqual(handler.save_file_path, expected_file) with open(expected_file) as f: report = json.load(f)