Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions docs/03_reference/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -1329,7 +1329,7 @@ corresponds to the `-N --name name` option of _robot_

## no-status-rc

Type: `bool | Flag | None`
Type: `bool | Flag | Condition | None`

Sets the return code to zero regardless of failures
in test cases. Error codes are returned normally.
Expand All @@ -1341,6 +1341,11 @@ Examples:
no-status-rc = true
```

```toml
# decide from the current branch
no-status-rc = { if = "environ.get('CI_COMMIT_REF_NAME') == 'main'" }
```

corresponds to the `--nostatusrc` option of _robot_

## output
Expand Down Expand Up @@ -2271,7 +2276,7 @@ corresponds to the `-N --name name` option of _robot_

## rebot.no-status-rc

Type: `bool | Flag | None`
Type: `bool | Flag | Condition | None`

Sets the return code to zero regardless of failures
in test cases. Error codes are returned normally.
Expand All @@ -2283,6 +2288,11 @@ Examples:
no-status-rc = true
```

```toml
# decide from the current branch
no-status-rc = { if = "environ.get('CI_COMMIT_REF_NAME') == 'main'" }
```

corresponds to the `--nostatusrc` option of _robot_

## rebot.output
Expand Down
37 changes: 22 additions & 15 deletions docs/public/schemas/robot.toml.json
Original file line number Diff line number Diff line change
Expand Up @@ -559,12 +559,7 @@
],
"markdownDescription": "Condition to evaluate. This must be a Python \"eval\" expression.\nFor security reasons, only certain expressions and functions are allowed.\n\nExamples:\n```toml\nif = \"re.match(r'^\\d+$', environ.get('TEST_VAR', ''))\"\nif = \"platform.system() == 'Linux'\"\n```\n\nsee also `expr` for allowed global names.\n",
"title": "If",
"type": "string",
"x-taplo": {
"links": {
"key": "https://robotcode.io/03_reference/config#profile-enabled-if"
}
}
"type": "string"
}
},
"required": [
Expand Down Expand Up @@ -2167,16 +2162,20 @@
"default"
]
},
{
"$ref": "#/definitions/Condition"
},
{
"type": "null"
}
],
"default": null,
"description": "Sets the return code to zero regardless of failures\nin test cases. Error codes are returned normally.\n\nExamples:\n\n```toml\n# always exit 0 regardless of failed tests\nno-status-rc = true\n```\n\ncorresponds to the `--nostatusrc` option of _robot_\n",
"description": "Sets the return code to zero regardless of failures\nin test cases. Error codes are returned normally.\n\nExamples:\n\n```toml\n# always exit 0 regardless of failed tests\nno-status-rc = true\n```\n\n```toml\n# decide from the current branch\nno-status-rc = { if = \"environ.get('CI_COMMIT_REF_NAME') == 'main'\" }\n```\n\ncorresponds to the `--nostatusrc` option of _robot_\n",
"examples": [
"# always exit 0 regardless of failed tests\nno-status-rc = true"
"# always exit 0 regardless of failed tests\nno-status-rc = true",
"# decide from the current branch\nno-status-rc = { if = \"environ.get('CI_COMMIT_REF_NAME') == 'main'\" }"
],
"markdownDescription": "Sets the return code to zero regardless of failures\nin test cases. Error codes are returned normally.\n\nExamples:\n\n```toml\n# always exit 0 regardless of failed tests\nno-status-rc = true\n```\n\ncorresponds to the `--nostatusrc` option of _robot_\n",
"markdownDescription": "Sets the return code to zero regardless of failures\nin test cases. Error codes are returned normally.\n\nExamples:\n\n```toml\n# always exit 0 regardless of failed tests\nno-status-rc = true\n```\n\n```toml\n# decide from the current branch\nno-status-rc = { if = \"environ.get('CI_COMMIT_REF_NAME') == 'main'\" }\n```\n\ncorresponds to the `--nostatusrc` option of _robot_\n",
"title": "No Status Rc",
"x-taplo": {
"links": {
Expand Down Expand Up @@ -4865,16 +4864,20 @@
"default"
]
},
{
"$ref": "#/definitions/Condition"
},
{
"type": "null"
}
],
"default": null,
"description": "Sets the return code to zero regardless of failures\nin test cases. Error codes are returned normally.\n\nExamples:\n\n```toml\n# always exit 0 regardless of failed tests\nno-status-rc = true\n```\n\ncorresponds to the `--nostatusrc` option of _robot_\n",
"description": "Sets the return code to zero regardless of failures\nin test cases. Error codes are returned normally.\n\nExamples:\n\n```toml\n# always exit 0 regardless of failed tests\nno-status-rc = true\n```\n\n```toml\n# decide from the current branch\nno-status-rc = { if = \"environ.get('CI_COMMIT_REF_NAME') == 'main'\" }\n```\n\ncorresponds to the `--nostatusrc` option of _robot_\n",
"examples": [
"# always exit 0 regardless of failed tests\nno-status-rc = true"
"# always exit 0 regardless of failed tests\nno-status-rc = true",
"# decide from the current branch\nno-status-rc = { if = \"environ.get('CI_COMMIT_REF_NAME') == 'main'\" }"
],
"markdownDescription": "Sets the return code to zero regardless of failures\nin test cases. Error codes are returned normally.\n\nExamples:\n\n```toml\n# always exit 0 regardless of failed tests\nno-status-rc = true\n```\n\ncorresponds to the `--nostatusrc` option of _robot_\n",
"markdownDescription": "Sets the return code to zero regardless of failures\nin test cases. Error codes are returned normally.\n\nExamples:\n\n```toml\n# always exit 0 regardless of failed tests\nno-status-rc = true\n```\n\n```toml\n# decide from the current branch\nno-status-rc = { if = \"environ.get('CI_COMMIT_REF_NAME') == 'main'\" }\n```\n\ncorresponds to the `--nostatusrc` option of _robot_\n",
"title": "No Status Rc",
"x-taplo": {
"links": {
Expand Down Expand Up @@ -8106,16 +8109,20 @@
"default"
]
},
{
"$ref": "#/definitions/Condition"
},
{
"type": "null"
}
],
"default": null,
"description": "Sets the return code to zero regardless of failures\nin test cases. Error codes are returned normally.\n\nExamples:\n\n```toml\n# always exit 0 regardless of failed tests\nno-status-rc = true\n```\n\ncorresponds to the `--nostatusrc` option of _robot_\n",
"description": "Sets the return code to zero regardless of failures\nin test cases. Error codes are returned normally.\n\nExamples:\n\n```toml\n# always exit 0 regardless of failed tests\nno-status-rc = true\n```\n\n```toml\n# decide from the current branch\nno-status-rc = { if = \"environ.get('CI_COMMIT_REF_NAME') == 'main'\" }\n```\n\ncorresponds to the `--nostatusrc` option of _robot_\n",
"examples": [
"# always exit 0 regardless of failed tests\nno-status-rc = true"
"# always exit 0 regardless of failed tests\nno-status-rc = true",
"# decide from the current branch\nno-status-rc = { if = \"environ.get('CI_COMMIT_REF_NAME') == 'main'\" }"
],
"markdownDescription": "Sets the return code to zero regardless of failures\nin test cases. Error codes are returned normally.\n\nExamples:\n\n```toml\n# always exit 0 regardless of failed tests\nno-status-rc = true\n```\n\ncorresponds to the `--nostatusrc` option of _robot_\n",
"markdownDescription": "Sets the return code to zero regardless of failures\nin test cases. Error codes are returned normally.\n\nExamples:\n\n```toml\n# always exit 0 regardless of failed tests\nno-status-rc = true\n```\n\n```toml\n# decide from the current branch\nno-status-rc = { if = \"environ.get('CI_COMMIT_REF_NAME') == 'main'\" }\n```\n\ncorresponds to the `--nostatusrc` option of _robot_\n",
"title": "No Status Rc",
"x-taplo": {
"links": {
Expand Down
7 changes: 6 additions & 1 deletion packages/robot/src/robotcode/robot/config/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -682,7 +682,7 @@ class CommonOptions(RobotBaseOptions):
robot_priority=500,
robot_short_name="N",
)
no_status_rc: Union[bool, Flag, None] = field(
no_status_rc: Union[bool, Flag, Condition, None] = field(
description="""\
Sets the return code to zero regardless of failures
in test cases. Error codes are returned normally.
Expand All @@ -694,6 +694,11 @@ class CommonOptions(RobotBaseOptions):
no-status-rc = true
```

```toml
# decide from the current branch
no-status-rc = { if = "environ.get('CI_COMMIT_REF_NAME') == 'main'" }
```

corresponds to the `--nostatusrc` option of _robot_
""",
robot_name="statusrc",
Expand Down
10 changes: 10 additions & 0 deletions scripts/create_robot_toml_json_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,16 @@ def process_def(def_name: str, path_prefix: str) -> None:
if isinstance(root_ref, str) and root_ref.startswith("#/$defs/"):
process_def(root_ref.rsplit("/", 1)[-1], "")

# Condition is shared by enabled, hidden, and conditional flag fields, so
# its nested key has no single context-independent documentation anchor.
condition_schema = defs.get("Condition")
if isinstance(condition_schema, dict):
condition_properties = condition_schema.get("properties")
if isinstance(condition_properties, dict):
condition_if = condition_properties.get("if")
if isinstance(condition_if, dict):
condition_if.pop("x-taplo", None)


def _convert_to_draft7_schema(schema_dict: dict) -> None:
"""Convert generated schema to draft-07 compatible structure in-place."""
Expand Down
10 changes: 9 additions & 1 deletion scripts/generate_rf_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@
# --- numeric options ---
"--consolewidth": ["console-width = 100"],
"--maxassignlength": ["max-assign-length = 200"],
"--maxerrorlines": ["max-error-lines = 40"],
"--maxerrorlines": ["max-error-lines = 40", 'max-error-lines = "NONE"'],
"--suitestatlevel": ["suite-stat-level = 2"],
# --- single-string options (paths, names, titles, timestamps) ---
"--debugfile": ['debug-file = "debug.log"'],
Expand Down Expand Up @@ -161,6 +161,7 @@
"--splitlog": ["split-log = true"],
"--nostatusrc": [
"# always exit 0 regardless of failed tests\nno-status-rc = true",
"# decide from the current branch\nno-status-rc = { if = \"environ.get('CI_COMMIT_REF_NAME') == 'main'\" }",
],
"--timestampoutputs": ["timestamp-outputs = true"],
}
Expand Down Expand Up @@ -345,6 +346,8 @@ def get_type(
) -> str:
if not option.get("param"):
if is_flag:
if name == "no_status_rc":
return "Union[bool, Flag, Condition, None]"
return "Union[bool, Flag, None]"

return "Optional[bool]"
Expand Down Expand Up @@ -421,6 +424,9 @@ def build_class_fields(output: List[str], opts: Dict[str, Any], extend: bool = F
output.append(" robot_is_flag=True,")
if not flag_default:
output.append(f" robot_flag_default={flag_default},")
alias = name.replace("_", "-")
if alias != name:
output.append(f' alias="{alias}",')
output.append(" )")

return result
Expand Down Expand Up @@ -549,6 +555,8 @@ def build_class_fields(output: List[str], opts: Dict[str, Any], extend: bool = F
tool="testdoc",
)

output.extend(["", ""])

model_file = Path("packages/robot/src/robotcode/robot/config/model.py")
original_lines = model_file.read_text().splitlines()

Expand Down
54 changes: 54 additions & 0 deletions tests/robotcode/robot/config/test_profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -374,3 +374,57 @@ def test_type_that_wants_alist_should_throw_an_error() -> None:
"""
with pytest.raises(TypeError, match=r".*Value '.*' must be of type.*"):
load_robot_config_from_robot_toml_str(data)


_STATUS_RC_FLAGS = {"--nostatusrc", "--statusrc"}


def _status_rc_flags(data: str, *profiles: str, rebot: bool = False) -> list[str]:
config = load_robot_config_from_robot_toml_str(data)
options = config.rebot if rebot else config.combine_profiles(*profiles)
assert options is not None
return [arg for arg in options.evaluated().build_command_line() if arg in _STATUS_RC_FLAGS]


@pytest.mark.parametrize(
("value", "expected"),
[
("true", ["--nostatusrc"]),
("false", ["--statusrc"]),
('"on"', ["--nostatusrc"]),
('"off"', ["--statusrc"]),
('"default"', []),
],
)
def test_no_status_rc_literals_keep_flag_semantics(value: str, expected: list[str]) -> None:
assert _status_rc_flags(f"no-status-rc = {value}") == expected


@pytest.mark.parametrize(("section", "rebot"), [("", False), ("[rebot]\n", True)])
@pytest.mark.parametrize(
("condition", "expected"),
[("True", ["--nostatusrc"]), ("False", ["--statusrc"])],
)
def test_no_status_rc_accepts_conditions(
section: str,
rebot: bool,
condition: str,
expected: list[str],
) -> None:
assert _status_rc_flags(f'{section}no-status-rc = {{ if = "{condition}" }}', rebot=rebot) == expected


def test_no_status_rc_rejects_invalid_condition() -> None:
data = """\
[profiles.ci]
no-status-rc = { if = "environ.get('CI') = 'true'" }
"""

with pytest.raises(
ValueError,
match=(
r"^Evaluation of 'no_status_rc' failed: EvaluationError: "
r"Evaluation of \"environ\.get\('CI'\) = 'true'\" failed: invalid syntax"
),
):
_status_rc_flags(data, "ci")
10 changes: 10 additions & 0 deletions tests/robotcode/robot/config/test_schema.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import json
from pathlib import Path


def test_shared_condition_has_no_context_specific_documentation_link() -> None:
schema_path = Path(__file__).parents[4] / "docs/public/schemas/robot.toml.json"
schema = json.loads(schema_path.read_text(encoding="utf-8"))
condition_if = schema["definitions"]["Condition"]["properties"]["if"]

assert "x-taplo" not in condition_if
64 changes: 64 additions & 0 deletions tests/robotcode/runner/cli/test_conditional_no_status_rc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import os
import subprocess
import sys
from pathlib import Path

import pytest


@pytest.mark.parametrize(
("branch", "expected_flag", "expected_returncode"),
[
("main", "--nostatusrc", 0),
("feature/example", "--statusrc", 1),
],
)
def test_ci_profile_conditional_no_status_rc_controls_robot_exit_status(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
branch: str,
expected_flag: str,
expected_returncode: int,
) -> None:
(tmp_path / "robot.toml").write_text(
"""\
default-profiles = ["ci"]

[profiles.ci]
enabled = { if = "environ.get('CI') == 'true'" }
no-status-rc = { if = "environ.get('CI_COMMIT_REF_NAME') == 'main'" }
""",
encoding="utf-8",
)
(tmp_path / "suite.robot").write_text(
"""\
*** Test Cases ***
Fails
Fail expected failure
""",
encoding="utf-8",
)
monkeypatch.setenv("CI", "true")
monkeypatch.setenv("CI_COMMIT_REF_NAME", branch)
user_dir = tmp_path / "user"
for name in ("HOME", "XDG_CONFIG_HOME", "APPDATA", "LOCALAPPDATA"):
monkeypatch.setenv(name, str(user_dir))
monkeypatch.setenv("ROBOTCODE_CACHE_DIR", str(tmp_path / "cache"))

result = subprocess.run(
[sys.executable, "-m", "robotcode.cli", "--verbose", "run", "suite.robot"],
cwd=tmp_path,
capture_output=True,
text=True,
timeout=120,
env=os.environ.copy(),
)
output = result.stdout + result.stderr
unexpected_flag = "--statusrc" if expected_flag == "--nostatusrc" else "--nostatusrc"

assert result.returncode == expected_returncode, output
assert "Selected profiles: ci" in output
assert 'Using profile "ci".' in output
assert "1 test, 0 passed, 1 failed" in output
assert expected_flag in output
assert unexpected_flag not in output