-
Notifications
You must be signed in to change notification settings - Fork 334
[harness] Preserve harness artifact values and write exit codes #2286
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -2,11 +2,19 @@ | |||||||||||||||||
| # All rights reserved. | ||||||||||||||||||
| # SPDX-License-Identifier: Apache-2.0 | ||||||||||||||||||
|
|
||||||||||||||||||
| from contextlib import contextmanager | ||||||||||||||||||
| import json | ||||||||||||||||||
|
|
||||||||||||||||||
| import pytest | ||||||||||||||||||
|
|
||||||||||||||||||
| from nemo_retriever.harness.artifact_writer import artifact_paths, ArtifactWriter, capture_output_to_log, redact | ||||||||||||||||||
| from nemo_retriever.harness.artifact_writer import ( | ||||||||||||||||||
| append_text, | ||||||||||||||||||
| artifact_paths, | ||||||||||||||||||
| ArtifactWriter, | ||||||||||||||||||
| capture_output_to_log, | ||||||||||||||||||
| redact, | ||||||||||||||||||
| ) | ||||||||||||||||||
| from nemo_retriever.harness.beir_runner import _write_trec_run | ||||||||||||||||||
| from nemo_retriever.harness.contracts import ( | ||||||||||||||||||
| EXIT_ARTIFACT_WRITE_FAILURE, | ||||||||||||||||||
| EXIT_INGEST_FAILURE, | ||||||||||||||||||
|
|
@@ -91,6 +99,42 @@ def test_redact_recurses_into_structured_override_values(): | |||||||||||||||||
| assert redact(override) == 'query={"reranker_api_key": "<redacted>", "top_k": 10}' | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
||||||||||||||||||
| def test_redact_preserves_token_limits(): | ||||||||||||||||||
| payload = { | ||||||||||||||||||
| "reranker_api_key": "secret-value", | ||||||||||||||||||
| "webhookUrl": "https://hooks.example.invalid/secret", | ||||||||||||||||||
| "webhooks": ["https://hooks.example.invalid/one"], | ||||||||||||||||||
| "passwords": ["password-value"], | ||||||||||||||||||
| "client_secrets": ["client-secret"], | ||||||||||||||||||
| "access_tokens": ["access-token"], | ||||||||||||||||||
| "caption_max_tokens": 77, | ||||||||||||||||||
| "text_chunk_overlap_tokens": 12, | ||||||||||||||||||
| "tokenizer_model": "model-name", | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| assert redact(payload) == { | ||||||||||||||||||
| "reranker_api_key": "<redacted>", | ||||||||||||||||||
| "webhookUrl": "<redacted>", | ||||||||||||||||||
| "webhooks": "<redacted>", | ||||||||||||||||||
| "passwords": "<redacted>", | ||||||||||||||||||
| "client_secrets": "<redacted>", | ||||||||||||||||||
| "access_tokens": "<redacted>", | ||||||||||||||||||
| "caption_max_tokens": 77, | ||||||||||||||||||
| "text_chunk_overlap_tokens": 12, | ||||||||||||||||||
| "tokenizer_model": "model-name", | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
||||||||||||||||||
| def test_text_artifact_writes_are_classified(tmp_path): | ||||||||||||||||||
| with pytest.raises(HarnessRunError) as log_error: | ||||||||||||||||||
| append_text(tmp_path, "log line") | ||||||||||||||||||
| with pytest.raises(HarnessRunError) as trec_error: | ||||||||||||||||||
| _write_trec_run(tmp_path, {"query": {"document": 1.0}}) | ||||||||||||||||||
|
|
||||||||||||||||||
| assert log_error.value.exit_code == EXIT_ARTIFACT_WRITE_FAILURE | ||||||||||||||||||
| assert trec_error.value.exit_code == EXIT_ARTIFACT_WRITE_FAILURE | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
||||||||||||||||||
| def test_invalid_run_config_preserves_existing_artifacts(tmp_path): | ||||||||||||||||||
| (tmp_path / "results.json").write_text('{"old": true}', encoding="utf-8") | ||||||||||||||||||
| (tmp_path / "lancedb").mkdir() | ||||||||||||||||||
|
|
@@ -222,6 +266,35 @@ def fail_writer(**kwargs): | |||||||||||||||||
| assert error.value.failure.message == "OSError: artifact directory unavailable" | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
||||||||||||||||||
| def test_run_benchmark_preserves_run_log_write_failure(monkeypatch, tmp_path): | ||||||||||||||||||
| import nemo_retriever.harness.execution as execution | ||||||||||||||||||
| from nemo_retriever.harness.json_io import artifact_write_error | ||||||||||||||||||
|
|
||||||||||||||||||
| class FakeIngestPlan: | ||||||||||||||||||
| documents = () | ||||||||||||||||||
|
|
||||||||||||||||||
| @contextmanager | ||||||||||||||||||
| def fail_log_capture(*args, **kwargs): | ||||||||||||||||||
| raise artifact_write_error(OSError("run log unavailable")) | ||||||||||||||||||
| yield | ||||||||||||||||||
|
Comment on lines
+276
to
+279
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Prompt To Fix With AIThis is a comment left during a code review.
Path: nemo_retriever/tests/test_harness_artifacts.py
Line: 276-279
Comment:
The `yield` after `raise` is unreachable at runtime but is semantically required: Python classifies a function as a generator function only when it contains at least one `yield` expression, regardless of reachability. Without the `yield`, `@contextmanager` would receive a plain function and would fail when `__enter__` calls `next()`. A brief comment prevents future confusion or spurious linter suppression.
```suggestion
@contextmanager
def fail_log_capture(*args, **kwargs):
raise artifact_write_error(OSError("run log unavailable"))
yield # unreachable; required to make this a generator function for @contextmanager
```
How can I resolve this? If you propose a fix, please make it concise.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time! |
||||||||||||||||||
|
|
||||||||||||||||||
| monkeypatch.setattr(execution, "resolve_ingest_plan", lambda request: FakeIngestPlan()) | ||||||||||||||||||
| monkeypatch.setattr(execution, "run_ingest_workflow", lambda plan, dry_run: {}) | ||||||||||||||||||
| monkeypatch.setattr(execution, "resolve_query_plan", lambda request: object()) | ||||||||||||||||||
| monkeypatch.setattr(execution, "query_plan_payload", lambda plan: {}) | ||||||||||||||||||
| monkeypatch.setattr(execution, "capture_output_to_log", fail_log_capture) | ||||||||||||||||||
|
|
||||||||||||||||||
| outcome = run_benchmark( | ||||||||||||||||||
| "jp20_smoke", | ||||||||||||||||||
| output_dir=str(tmp_path / "run"), | ||||||||||||||||||
| overrides=(f'dataset.path="{tmp_path}"',), | ||||||||||||||||||
| ) | ||||||||||||||||||
|
|
||||||||||||||||||
| assert outcome.exit_code == EXIT_ARTIFACT_WRITE_FAILURE | ||||||||||||||||||
| assert outcome.results["failure"]["failure_reason"] == "artifact_write_failed" | ||||||||||||||||||
| assert outcome.results["failure"]["message"] == "OSError: run log unavailable" | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
||||||||||||||||||
| def test_run_benchmark_keeps_non_write_failures_internal(monkeypatch, tmp_path): | ||||||||||||||||||
| import nemo_retriever.harness.execution as execution | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The check
parts[index - 1] in _TOKEN_CREDENTIAL_QUALIFIERSonly examines the word atindex - 1. A key likeservice_account_tokenswould not be redacted because "account" is the immediate predecessor of "tokens", even though "service" (a qualifier) appears atindex - 2. Similarlyoauth_app_tokens,api_service_tokens, etc. would slip through. The fix is to check any part between position 0 andindex - 1for a qualifier, rather than onlyindex - 1.Prompt To Fix With AI