test(integ): cover the new set options and the export attribute

43 GNU-pinned cases: `assign/export_attr.json`, plus noclobber, noexec,
verbose and braceexpand under `setopt/`, which had errexit, noglob,
nounset, pipefail and xtrace but none of stage 1. The noexec cases are
what caught the subshell bug.

Both runners and `state_store` wrote into `ws.env`, which the read-only
projection refuses, so the battery could not start on either host. A
meta-test now scans for that write shape across both trees: mypy runs on
`mirage/` alone, so `integ/` and `tests/` are exactly where it recurs
unseen.
This commit is contained in:
Zecheng Zhang
2026-08-15 14:51:11 -07:00
parent 8dd49a2d2b
commit 6ecac7c4b8
11 changed files with 1588 additions and 11 deletions
+820
View File
@@ -0,0 +1,820 @@
{
"cases": [
{
"id": "export_env_carries_only_exported",
"seq": 930400,
"targets": [
"ram",
"disk",
"redis",
"opfs",
"s3",
"databricks",
"gridfs",
"s3-prefix",
"databricks-prefix",
"gridfs-prefix",
"hf",
"hf-prefix",
"dropbox",
"dropbox-root",
"onedrive",
"sharepoint",
"sharepoint-prefix",
"ssh",
"nextcloud",
"gdrive",
"gdrive-folder",
"box"
],
"command": "QEA=hello; export QEB=world; env | grep -E \"^QE(A|B)=\"",
"expect": {
"exit": 0,
"stdout": "QEB=world\n",
"stderr": ""
}
},
{
"id": "export_declared_unset_stays_out_of_env",
"seq": 930401,
"targets": [
"ram",
"disk",
"redis",
"opfs",
"s3",
"databricks",
"gridfs",
"s3-prefix",
"databricks-prefix",
"gridfs-prefix",
"hf",
"hf-prefix",
"dropbox",
"dropbox-root",
"onedrive",
"sharepoint",
"sharepoint-prefix",
"ssh",
"nextcloud",
"gdrive",
"gdrive-folder",
"box"
],
"command": "export QEC; env | grep -c \"^QEC=\"",
"expect": {
"exit": 1,
"stdout": "0\n",
"stderr": ""
}
},
{
"id": "export_n_drops_from_env",
"seq": 930402,
"targets": [
"ram",
"disk",
"redis",
"opfs",
"s3",
"databricks",
"gridfs",
"s3-prefix",
"databricks-prefix",
"gridfs-prefix",
"hf",
"hf-prefix",
"dropbox",
"dropbox-root",
"onedrive",
"sharepoint",
"sharepoint-prefix",
"ssh",
"nextcloud",
"gdrive",
"gdrive-folder",
"box"
],
"command": "export QED=world; export -n QED; env | grep -c \"^QED=\"",
"expect": {
"exit": 1,
"stdout": "0\n",
"stderr": ""
}
},
{
"id": "export_allexport_marks_only_while_on",
"seq": 930403,
"targets": [
"ram",
"disk",
"redis",
"opfs",
"s3",
"databricks",
"gridfs",
"s3-prefix",
"databricks-prefix",
"gridfs-prefix",
"hf",
"hf-prefix",
"dropbox",
"dropbox-root",
"onedrive",
"sharepoint",
"sharepoint-prefix",
"ssh",
"nextcloud",
"gdrive",
"gdrive-folder",
"box"
],
"command": "QEF=1; set -a; QEG=2; set +a; QEH=3; env | grep -cE \"^QE(F|G|H)=\"",
"expect": {
"exit": 0,
"stdout": "1\n",
"stderr": ""
}
},
{
"id": "declare_p_plain_and_exported",
"seq": 930404,
"targets": [
"ram",
"disk",
"redis",
"opfs",
"s3",
"databricks",
"gridfs",
"s3-prefix",
"databricks-prefix",
"gridfs-prefix",
"hf",
"hf-prefix",
"dropbox",
"dropbox-root",
"onedrive",
"sharepoint",
"sharepoint-prefix",
"ssh",
"nextcloud",
"gdrive",
"gdrive-folder",
"box"
],
"command": "QEI=hello; export QEJ=world; declare -p QEI QEJ",
"expect": {
"exit": 0,
"stdout": "declare -- QEI=\"hello\"\ndeclare -x QEJ=\"world\"\n",
"stderr": ""
}
},
{
"id": "declare_p_declared_but_unset",
"seq": 930405,
"targets": [
"ram",
"disk",
"redis",
"opfs",
"s3",
"databricks",
"gridfs",
"s3-prefix",
"databricks-prefix",
"gridfs-prefix",
"hf",
"hf-prefix",
"dropbox",
"dropbox-root",
"onedrive",
"sharepoint",
"sharepoint-prefix",
"ssh",
"nextcloud",
"gdrive",
"gdrive-folder",
"box"
],
"command": "export QEK; declare -p QEK",
"expect": {
"exit": 0,
"stdout": "declare -x QEK\n",
"stderr": ""
}
},
{
"id": "declare_x_marks_existing_name",
"seq": 930406,
"targets": [
"ram",
"disk",
"redis",
"opfs",
"s3",
"databricks",
"gridfs",
"s3-prefix",
"databricks-prefix",
"gridfs-prefix",
"hf",
"hf-prefix",
"dropbox",
"dropbox-root",
"onedrive",
"sharepoint",
"sharepoint-prefix",
"ssh",
"nextcloud",
"gdrive",
"gdrive-folder",
"box"
],
"command": "QEL=val; declare -x QEL; declare -p QEL",
"expect": {
"exit": 0,
"stdout": "declare -x QEL=\"val\"\n",
"stderr": ""
}
},
{
"id": "export_n_leaves_ordinary_variable",
"seq": 930407,
"targets": [
"ram",
"disk",
"redis",
"opfs",
"s3",
"databricks",
"gridfs",
"s3-prefix",
"databricks-prefix",
"gridfs-prefix",
"hf",
"hf-prefix",
"dropbox",
"dropbox-root",
"onedrive",
"sharepoint",
"sharepoint-prefix",
"ssh",
"nextcloud",
"gdrive",
"gdrive-folder",
"box"
],
"command": "export QEM=world; export -n QEM; declare -p QEM",
"expect": {
"exit": 0,
"stdout": "declare -- QEM=\"world\"\n",
"stderr": ""
}
},
{
"id": "declare_p_attribute_order",
"seq": 930408,
"targets": [
"ram",
"disk",
"redis",
"opfs",
"s3",
"databricks",
"gridfs",
"s3-prefix",
"databricks-prefix",
"gridfs-prefix",
"hf",
"hf-prefix",
"dropbox",
"dropbox-root",
"onedrive",
"sharepoint",
"sharepoint-prefix",
"ssh",
"nextcloud",
"gdrive",
"gdrive-folder",
"box"
],
"command": "export QEN=v; readonly QEN; declare -p QEN",
"expect": {
"exit": 0,
"stdout": "declare -rx QEN=\"v\"\n",
"stderr": ""
}
},
{
"id": "declare_p_unknown_name_exits_1",
"seq": 930409,
"targets": [
"ram",
"disk",
"redis",
"opfs",
"s3",
"databricks",
"gridfs",
"s3-prefix",
"databricks-prefix",
"gridfs-prefix",
"hf",
"hf-prefix",
"dropbox",
"dropbox-root",
"onedrive",
"sharepoint",
"sharepoint-prefix",
"ssh",
"nextcloud",
"gdrive",
"gdrive-folder",
"box"
],
"command": "QEO=good; declare -p QEO QENOPE",
"expect": {
"exit": 1,
"stdout": "declare -- QEO=\"good\"\n",
"stderr": "bash: declare: QENOPE: not found\n"
}
},
{
"id": "export_p_lists_exported_only",
"seq": 930410,
"targets": [
"ram",
"disk",
"redis",
"opfs",
"s3",
"databricks",
"gridfs",
"s3-prefix",
"databricks-prefix",
"gridfs-prefix",
"hf",
"hf-prefix",
"dropbox",
"dropbox-root",
"onedrive",
"sharepoint",
"sharepoint-prefix",
"ssh",
"nextcloud",
"gdrive",
"gdrive-folder",
"box"
],
"command": "QEP=hello; export QEQ=world; export QER; export -p | grep -E \"^declare -x QE(P|Q|R)\"",
"expect": {
"exit": 0,
"stdout": "declare -x QEQ=\"world\"\ndeclare -x QER\n",
"stderr": ""
}
},
{
"id": "printenv_refuses_a_plain_variable",
"seq": 930411,
"targets": [
"ram",
"disk",
"redis",
"opfs",
"s3",
"databricks",
"gridfs",
"s3-prefix",
"databricks-prefix",
"gridfs-prefix",
"hf",
"hf-prefix",
"dropbox",
"dropbox-root",
"onedrive",
"sharepoint",
"sharepoint-prefix",
"ssh",
"nextcloud",
"gdrive",
"gdrive-folder",
"box"
],
"command": "QES=plain; printenv QES",
"expect": {
"exit": 1,
"stdout": "",
"stderr": ""
}
},
{
"id": "printenv_reads_an_exported_one",
"seq": 930412,
"targets": [
"ram",
"disk",
"redis",
"opfs",
"s3",
"databricks",
"gridfs",
"s3-prefix",
"databricks-prefix",
"gridfs-prefix",
"hf",
"hf-prefix",
"dropbox",
"dropbox-root",
"onedrive",
"sharepoint",
"sharepoint-prefix",
"ssh",
"nextcloud",
"gdrive",
"gdrive-folder",
"box"
],
"command": "export QET=e; printenv QET",
"expect": {
"exit": 0,
"stdout": "e\n",
"stderr": ""
}
},
{
"id": "plain_variable_still_expands",
"seq": 930413,
"targets": [
"ram",
"disk",
"redis",
"opfs",
"s3",
"databricks",
"gridfs",
"s3-prefix",
"databricks-prefix",
"gridfs-prefix",
"hf",
"hf-prefix",
"dropbox",
"dropbox-root",
"onedrive",
"sharepoint",
"sharepoint-prefix",
"ssh",
"nextcloud",
"gdrive",
"gdrive-folder",
"box"
],
"command": "QEU=hello; echo $QEU",
"expect": {
"exit": 0,
"stdout": "hello\n",
"stderr": ""
}
},
{
"id": "cd_exports_pwd_and_oldpwd",
"seq": 930414,
"targets": [
"ram",
"disk",
"redis",
"opfs",
"s3",
"databricks",
"gridfs",
"s3-prefix",
"databricks-prefix",
"gridfs-prefix",
"hf",
"hf-prefix",
"dropbox",
"dropbox-root",
"onedrive",
"sharepoint",
"sharepoint-prefix",
"ssh",
"nextcloud",
"gdrive",
"gdrive-folder",
"box"
],
"command": "cd /; mkdir -p /data/qed; cd /data/qed; cd /data; declare -p PWD OLDPWD",
"expect": {
"exit": 0,
"stdout": "declare -x PWD=\"/data\"\ndeclare -x OLDPWD=\"/data/qed\"\n",
"stderr": ""
}
},
{
"id": "export_refuses_invalid_identifier",
"seq": 930415,
"targets": [
"ram",
"disk",
"redis",
"opfs",
"s3",
"databricks",
"gridfs",
"s3-prefix",
"databricks-prefix",
"gridfs-prefix",
"hf",
"hf-prefix",
"dropbox",
"dropbox-root",
"onedrive",
"sharepoint",
"sharepoint-prefix",
"ssh",
"nextcloud",
"gdrive",
"gdrive-folder",
"box"
],
"command": "export 1QEBAD=x",
"expect": {
"exit": 1,
"stdout": "",
"stderr": "bash: export: `1QEBAD=x': not a valid identifier\n"
}
},
{
"id": "readonly_refuses_invalid_identifier",
"seq": 930416,
"targets": [
"ram",
"disk",
"redis",
"opfs",
"s3",
"databricks",
"gridfs",
"s3-prefix",
"databricks-prefix",
"gridfs-prefix",
"hf",
"hf-prefix",
"dropbox",
"dropbox-root",
"onedrive",
"sharepoint",
"sharepoint-prefix",
"ssh",
"nextcloud",
"gdrive",
"gdrive-folder",
"box"
],
"command": "readonly 2QEBAD=y",
"expect": {
"exit": 1,
"stdout": "",
"stderr": "bash: readonly: `2QEBAD=y': not a valid identifier\n"
}
},
{
"id": "local_refuses_invalid_identifier",
"seq": 930417,
"targets": [
"ram",
"disk",
"redis",
"opfs",
"s3",
"databricks",
"gridfs",
"s3-prefix",
"databricks-prefix",
"gridfs-prefix",
"hf",
"hf-prefix",
"dropbox",
"dropbox-root",
"onedrive",
"sharepoint",
"sharepoint-prefix",
"ssh",
"nextcloud",
"gdrive",
"gdrive-folder",
"box"
],
"command": "qe_fn() { local 3QEBAD=z; }; qe_fn",
"expect": {
"exit": 1,
"stdout": "",
"stderr": "bash: local: `3QEBAD=z': not a valid identifier\n"
}
},
{
"id": "declare_refuses_invalid_identifier",
"seq": 930418,
"targets": [
"ram",
"disk",
"redis",
"opfs",
"s3",
"databricks",
"gridfs",
"s3-prefix",
"databricks-prefix",
"gridfs-prefix",
"hf",
"hf-prefix",
"dropbox",
"dropbox-root",
"onedrive",
"sharepoint",
"sharepoint-prefix",
"ssh",
"nextcloud",
"gdrive",
"gdrive-folder",
"box"
],
"command": "declare 4QEBAD=w",
"expect": {
"exit": 1,
"stdout": "",
"stderr": "bash: declare: `4QEBAD=w': not a valid identifier\n"
}
},
{
"id": "export_bare_refuses_invalid_identifier",
"seq": 930419,
"targets": [
"ram",
"disk",
"redis",
"opfs",
"s3",
"databricks",
"gridfs",
"s3-prefix",
"databricks-prefix",
"gridfs-prefix",
"hf",
"hf-prefix",
"dropbox",
"dropbox-root",
"onedrive",
"sharepoint",
"sharepoint-prefix",
"ssh",
"nextcloud",
"gdrive",
"gdrive-folder",
"box"
],
"command": "export 5QEBAD",
"expect": {
"exit": 1,
"stdout": "",
"stderr": "bash: export: `5QEBAD': not a valid identifier\n"
}
},
{
"id": "export_refuses_subscript_target",
"seq": 930420,
"targets": [
"ram",
"disk",
"redis",
"opfs",
"s3",
"databricks",
"gridfs",
"s3-prefix",
"databricks-prefix",
"gridfs-prefix",
"hf",
"hf-prefix",
"dropbox",
"dropbox-root",
"onedrive",
"sharepoint",
"sharepoint-prefix",
"ssh",
"nextcloud",
"gdrive",
"gdrive-folder",
"box"
],
"command": "export qearr[0]=1",
"expect": {
"exit": 1,
"stdout": "",
"stderr": "bash: export: `qearr[0]': not a valid identifier\n"
}
},
{
"id": "export_reports_each_bad_operand",
"seq": 930421,
"targets": [
"ram",
"disk",
"redis",
"opfs",
"s3",
"databricks",
"gridfs",
"s3-prefix",
"databricks-prefix",
"gridfs-prefix",
"hf",
"hf-prefix",
"dropbox",
"dropbox-root",
"onedrive",
"sharepoint",
"sharepoint-prefix",
"ssh",
"nextcloud",
"gdrive",
"gdrive-folder",
"box"
],
"command": "export 6QEBAD=x 7QEBAD=y",
"expect": {
"exit": 1,
"stdout": "",
"stderr": "bash: export: `6QEBAD=x': not a valid identifier\nbash: export: `7QEBAD=y': not a valid identifier\n"
}
},
{
"id": "export_keeps_good_names_on_a_bad_line",
"seq": 930422,
"targets": [
"ram",
"disk",
"redis",
"opfs",
"s3",
"databricks",
"gridfs",
"s3-prefix",
"databricks-prefix",
"gridfs-prefix",
"hf",
"hf-prefix",
"dropbox",
"dropbox-root",
"onedrive",
"sharepoint",
"sharepoint-prefix",
"ssh",
"nextcloud",
"gdrive",
"gdrive-folder",
"box"
],
"command": "export QEV=1 8QEBAD=x QEW=2; declare -p QEV QEW",
"expect": {
"exit": 0,
"stdout": "declare -x QEV=\"1\"\ndeclare -x QEW=\"2\"\n",
"stderr": "bash: export: `8QEBAD=x': not a valid identifier\n"
}
},
{
"id": "export_quoted_empty_operand_is_refused",
"seq": 930423,
"targets": [
"ram",
"disk",
"redis",
"opfs",
"s3",
"databricks",
"gridfs",
"s3-prefix",
"databricks-prefix",
"gridfs-prefix",
"hf",
"hf-prefix",
"dropbox",
"dropbox-root",
"onedrive",
"sharepoint",
"sharepoint-prefix",
"ssh",
"nextcloud",
"gdrive",
"gdrive-folder",
"box"
],
"command": "export \"\"",
"expect": {
"exit": 1,
"stdout": "",
"stderr": "bash: export: `': not a valid identifier\n"
}
}
]
}
+106
View File
@@ -0,0 +1,106 @@
{
"cases": [
{
"id": "opt_braceexpand_on_by_default",
"seq": 502813,
"targets": [
"ram",
"disk",
"redis",
"opfs",
"s3",
"databricks",
"gridfs",
"s3-prefix",
"databricks-prefix",
"gridfs-prefix",
"hf",
"hf-prefix",
"dropbox",
"dropbox-root",
"onedrive",
"sharepoint",
"sharepoint-prefix",
"ssh",
"nextcloud",
"gdrive",
"gdrive-folder",
"box"
],
"command": "echo {a,b}",
"expect": {
"exit": 0,
"stdout": "a b\n",
"stderr": ""
}
},
{
"id": "opt_braceexpand_plus_b_keeps_literal",
"seq": 502814,
"targets": [
"ram",
"disk",
"redis",
"opfs",
"s3",
"databricks",
"gridfs",
"s3-prefix",
"databricks-prefix",
"gridfs-prefix",
"hf",
"hf-prefix",
"dropbox",
"dropbox-root",
"onedrive",
"sharepoint",
"sharepoint-prefix",
"ssh",
"nextcloud",
"gdrive",
"gdrive-folder",
"box"
],
"command": "(set +B; echo {a,b})",
"expect": {
"exit": 0,
"stdout": "{a,b}\n",
"stderr": ""
}
},
{
"id": "opt_braceexpand_listed_off_under_plus_b",
"seq": 502815,
"targets": [
"ram",
"disk",
"redis",
"opfs",
"s3",
"databricks",
"gridfs",
"s3-prefix",
"databricks-prefix",
"gridfs-prefix",
"hf",
"hf-prefix",
"dropbox",
"dropbox-root",
"onedrive",
"sharepoint",
"sharepoint-prefix",
"ssh",
"nextcloud",
"gdrive",
"gdrive-folder",
"box"
],
"command": "(set +B; set -o | grep braceexpand)",
"expect": {
"exit": 0,
"stdout": "braceexpand \toff\n",
"stderr": ""
}
}
]
}
+242
View File
@@ -0,0 +1,242 @@
{
"cases": [
{
"id": "opt_noclobber_refuses_overwrite",
"seq": 502800,
"targets": [
"ram",
"disk",
"redis",
"opfs",
"s3",
"databricks",
"gridfs",
"s3-prefix",
"databricks-prefix",
"gridfs-prefix",
"hf",
"hf-prefix",
"dropbox",
"dropbox-root",
"onedrive",
"sharepoint",
"sharepoint-prefix",
"ssh",
"nextcloud",
"gdrive",
"gdrive-folder",
"box"
],
"command": "echo a > /data/zqs1.txt; (set -C; echo b > /data/zqs1.txt); echo rc=$?; cat /data/zqs1.txt",
"expect": {
"exit": 0,
"stdout": "rc=1\na\n",
"stderr": "/data/zqs1.txt: cannot overwrite existing file\n"
}
},
{
"id": "opt_noclobber_pipe_override_writes",
"seq": 502801,
"targets": [
"ram",
"disk",
"redis",
"opfs",
"s3",
"databricks",
"gridfs",
"s3-prefix",
"databricks-prefix",
"gridfs-prefix",
"hf",
"hf-prefix",
"dropbox",
"dropbox-root",
"onedrive",
"sharepoint",
"sharepoint-prefix",
"ssh",
"nextcloud",
"gdrive",
"gdrive-folder",
"box"
],
"command": "echo a > /data/zqs2.txt; (set -C; echo b >| /data/zqs2.txt); echo rc=$?; cat /data/zqs2.txt",
"expect": {
"exit": 0,
"stdout": "rc=0\nb\n",
"stderr": ""
}
},
{
"id": "opt_noclobber_append_allowed",
"seq": 502802,
"targets": [
"ram",
"disk",
"redis",
"opfs",
"s3",
"databricks",
"gridfs",
"s3-prefix",
"databricks-prefix",
"gridfs-prefix",
"hf",
"hf-prefix",
"dropbox",
"dropbox-root",
"onedrive",
"sharepoint",
"sharepoint-prefix",
"ssh",
"nextcloud",
"gdrive",
"gdrive-folder",
"box"
],
"command": "echo a > /data/zqs3.txt; (set -C; echo b >> /data/zqs3.txt); echo rc=$?; cat /data/zqs3.txt",
"expect": {
"exit": 0,
"stdout": "rc=0\na\nb\n",
"stderr": ""
}
},
{
"id": "opt_noclobber_new_target_allowed",
"seq": 502803,
"targets": [
"ram",
"disk",
"redis",
"opfs",
"s3",
"databricks",
"gridfs",
"s3-prefix",
"databricks-prefix",
"gridfs-prefix",
"hf",
"hf-prefix",
"dropbox",
"dropbox-root",
"onedrive",
"sharepoint",
"sharepoint-prefix",
"ssh",
"nextcloud",
"gdrive",
"gdrive-folder",
"box"
],
"command": "(set -C; echo n > /data/zqs4.txt); echo rc=$?; cat /data/zqs4.txt",
"expect": {
"exit": 0,
"stdout": "rc=0\nn\n",
"stderr": ""
}
},
{
"id": "opt_noclobber_long_name_same_option",
"seq": 502804,
"targets": [
"ram",
"disk",
"redis",
"opfs",
"s3",
"databricks",
"gridfs",
"s3-prefix",
"databricks-prefix",
"gridfs-prefix",
"hf",
"hf-prefix",
"dropbox",
"dropbox-root",
"onedrive",
"sharepoint",
"sharepoint-prefix",
"ssh",
"nextcloud",
"gdrive",
"gdrive-folder",
"box"
],
"command": "echo a > /data/zqs5.txt; (set -o noclobber; echo b > /data/zqs5.txt); echo rc=$?",
"expect": {
"exit": 0,
"stdout": "rc=1\n",
"stderr": "/data/zqs5.txt: cannot overwrite existing file\n"
}
},
{
"id": "opt_noclobber_directory_wording",
"seq": 502805,
"targets": [
"ram",
"disk",
"redis",
"opfs",
"s3",
"databricks",
"gridfs",
"s3-prefix",
"databricks-prefix",
"gridfs-prefix",
"hf",
"hf-prefix",
"dropbox",
"dropbox-root",
"onedrive",
"sharepoint",
"sharepoint-prefix",
"ssh",
"nextcloud",
"gdrive",
"gdrive-folder",
"box"
],
"command": "mkdir -p /data/zqsd; (set -C; echo b > /data/zqsd); echo rc=$?",
"expect": {
"exit": 0,
"stdout": "rc=1\n",
"stderr": "/data/zqsd: Is a directory\n"
}
},
{
"id": "opt_noclobber_off_by_default",
"seq": 502806,
"targets": [
"ram",
"disk",
"redis",
"opfs",
"s3",
"databricks",
"gridfs",
"s3-prefix",
"databricks-prefix",
"gridfs-prefix",
"hf",
"hf-prefix",
"dropbox",
"dropbox-root",
"onedrive",
"sharepoint",
"sharepoint-prefix",
"ssh",
"nextcloud",
"gdrive",
"gdrive-folder",
"box"
],
"command": "echo a > /data/zqs6.txt; echo b > /data/zqs6.txt; echo rc=$?; cat /data/zqs6.txt",
"expect": {
"exit": 0,
"stdout": "rc=0\nb\n",
"stderr": ""
}
}
]
}
+140
View File
@@ -0,0 +1,140 @@
{
"cases": [
{
"id": "opt_noexec_skips_rest_of_subshell",
"seq": 502807,
"targets": [
"ram",
"disk",
"redis",
"opfs",
"s3",
"databricks",
"gridfs",
"s3-prefix",
"databricks-prefix",
"gridfs-prefix",
"hf",
"hf-prefix",
"dropbox",
"dropbox-root",
"onedrive",
"sharepoint",
"sharepoint-prefix",
"ssh",
"nextcloud",
"gdrive",
"gdrive-folder",
"box"
],
"command": "(set -n; echo hi); echo rc=$?",
"expect": {
"exit": 0,
"stdout": "rc=0\n",
"stderr": ""
}
},
{
"id": "opt_noexec_only_inside_the_subshell",
"seq": 502808,
"targets": [
"ram",
"disk",
"redis",
"opfs",
"s3",
"databricks",
"gridfs",
"s3-prefix",
"databricks-prefix",
"gridfs-prefix",
"hf",
"hf-prefix",
"dropbox",
"dropbox-root",
"onedrive",
"sharepoint",
"sharepoint-prefix",
"ssh",
"nextcloud",
"gdrive",
"gdrive-folder",
"box"
],
"command": "echo a; (set -n; echo b); echo c",
"expect": {
"exit": 0,
"stdout": "a\nc\n",
"stderr": ""
}
},
{
"id": "opt_noexec_plus_n_never_runs",
"seq": 502809,
"targets": [
"ram",
"disk",
"redis",
"opfs",
"s3",
"databricks",
"gridfs",
"s3-prefix",
"databricks-prefix",
"gridfs-prefix",
"hf",
"hf-prefix",
"dropbox",
"dropbox-root",
"onedrive",
"sharepoint",
"sharepoint-prefix",
"ssh",
"nextcloud",
"gdrive",
"gdrive-folder",
"box"
],
"command": "(set -n; set +n; echo after); echo rc=$?",
"expect": {
"exit": 0,
"stdout": "rc=0\n",
"stderr": ""
}
},
{
"id": "opt_noexec_does_not_leak_out",
"seq": 502810,
"targets": [
"ram",
"disk",
"redis",
"opfs",
"s3",
"databricks",
"gridfs",
"s3-prefix",
"databricks-prefix",
"gridfs-prefix",
"hf",
"hf-prefix",
"dropbox",
"dropbox-root",
"onedrive",
"sharepoint",
"sharepoint-prefix",
"ssh",
"nextcloud",
"gdrive",
"gdrive-folder",
"box"
],
"command": "(set -n; echo x); echo still-here",
"expect": {
"exit": 0,
"stdout": "still-here\n",
"stderr": ""
}
}
]
}
+68
View File
@@ -101,6 +101,74 @@
"stdout": "1\n",
"stderr": ""
}
},
{
"id": "opt_set_o_lists_option_state",
"seq": 502816,
"targets": [
"ram",
"disk",
"redis",
"opfs",
"s3",
"databricks",
"gridfs",
"s3-prefix",
"databricks-prefix",
"gridfs-prefix",
"hf",
"hf-prefix",
"dropbox",
"dropbox-root",
"onedrive",
"sharepoint",
"sharepoint-prefix",
"ssh",
"nextcloud",
"gdrive",
"gdrive-folder",
"box"
],
"command": "set -o | grep noclobber",
"expect": {
"exit": 0,
"stdout": "noclobber \toff\n",
"stderr": ""
}
},
{
"id": "opt_set_plus_o_is_re_readable",
"seq": 502817,
"targets": [
"ram",
"disk",
"redis",
"opfs",
"s3",
"databricks",
"gridfs",
"s3-prefix",
"databricks-prefix",
"gridfs-prefix",
"hf",
"hf-prefix",
"dropbox",
"dropbox-root",
"onedrive",
"sharepoint",
"sharepoint-prefix",
"ssh",
"nextcloud",
"gdrive",
"gdrive-folder",
"box"
],
"command": "set +o | grep -c '^set [-+]o noclobber$'",
"expect": {
"exit": 0,
"stdout": "1\n",
"stderr": ""
}
}
]
}
+106
View File
@@ -0,0 +1,106 @@
{
"cases": [
{
"id": "opt_verbose_same_line_echoes_nothing",
"seq": 502812,
"targets": [
"ram",
"disk",
"redis",
"opfs",
"s3",
"databricks",
"gridfs",
"s3-prefix",
"databricks-prefix",
"gridfs-prefix",
"hf",
"hf-prefix",
"dropbox",
"dropbox-root",
"onedrive",
"sharepoint",
"sharepoint-prefix",
"ssh",
"nextcloud",
"gdrive",
"gdrive-folder",
"box"
],
"command": "(set -v; echo hi)",
"expect": {
"exit": 0,
"stdout": "hi\n",
"stderr": ""
}
},
{
"id": "opt_verbose_subshell_read_as_one_unit",
"seq": 502813,
"targets": [
"ram",
"disk",
"redis",
"opfs",
"s3",
"databricks",
"gridfs",
"s3-prefix",
"databricks-prefix",
"gridfs-prefix",
"hf",
"hf-prefix",
"dropbox",
"dropbox-root",
"onedrive",
"sharepoint",
"sharepoint-prefix",
"ssh",
"nextcloud",
"gdrive",
"gdrive-folder",
"box"
],
"command": "(set -v\necho hi)",
"expect": {
"exit": 0,
"stdout": "hi\n",
"stderr": ""
}
},
{
"id": "opt_verbose_echoes_lines_read_while_on",
"seq": 502814,
"targets": [
"ram",
"disk",
"redis",
"opfs",
"s3",
"databricks",
"gridfs",
"s3-prefix",
"databricks-prefix",
"gridfs-prefix",
"hf",
"hf-prefix",
"dropbox",
"dropbox-root",
"onedrive",
"sharepoint",
"sharepoint-prefix",
"ssh",
"nextcloud",
"gdrive",
"gdrive-folder",
"box"
],
"command": "set -v\necho hi\nset +v\necho after",
"expect": {
"exit": 0,
"stdout": "hi\nafter\n",
"stderr": "echo hi\nset +v\n"
}
}
]
}
+7 -3
View File
@@ -2293,7 +2293,11 @@ async def open_target(
# (ntn's --notion-version off NOTION_API_VERSION) behaves differently
# with and without it, so the conformance runner passes the same map
# to the real binary and the comparison stays like for like.
ws.env.update(target.get("env", {}))
# Through the setter, not into the mapping: `ws.env` is a read-only
# projection of the variable records, and a target's declared
# environment is exported by definition -- a CLI reads it as a
# process environment, which carries exported names only.
ws.env = {**ws.env, **target.get("env", {})}
return ws, functools.partial(teardown_target, [ws], cleanups, service)
@@ -2316,8 +2320,8 @@ async def open_consistency(
# Same rule as open_target: a target's declared environment reaches
# every workspace a case can run against, or a consistency scenario
# would silently run under a different one.
read_ws.env.update(target.get("env", {}))
shadow_ws.env.update(target.get("env", {}))
read_ws.env = {**read_ws.env, **target.get("env", {})}
shadow_ws.env = {**shadow_ws.env, **target.get("env", {})}
return (
read_ws,
functools.partial(mutate_write, shadow_ws),
+6 -2
View File
@@ -85,7 +85,11 @@ async function runTarget(
// (ntn's --notion-version off NOTION_API_VERSION) behaves differently with
// and without it, so the conformance runner passes the same map to the real
// binary and the comparison stays like for like.
Object.assign(ws.env, target.env ?? {})
// Through the setter, not into the record: `ws.env` is a frozen
// projection of the variable records, and a target's declared
// environment is exported by definition -- a CLI reads it as a
// process environment, which carries exported names only.
ws.env = { ...ws.env, ...(target.env ?? {}) }
for (const mount of target.mounts) {
await seedFixture(ws, mount.fixture, mount.path, root)
if (mount.seed_root) await seedMountRoot(ws, mount.path)
@@ -166,7 +170,7 @@ async function runTarget(
// Same rule as the ordinary path: a target's declared environment reaches
// every workspace a case can run against, or a consistency scenario would
// silently run under a different one.
Object.assign(opened.ws.env, target.env ?? {})
opened.ws.env = { ...opened.ws.env, ...(target.env ?? {}) }
const { exitCode, out } = await runScenario(opened.ws, opened.mutate, c.scenario)
if (emit !== null) {
emit.push({ target: target.id, id: c.id, exit: exitCode, stdout: out, stderr: '' })
+4 -3
View File
@@ -24,6 +24,7 @@ import uuid # noqa: E402
from mirage import MountMode, Workspace # noqa: E402
from mirage.accessor.s3 import S3Config # noqa: E402
from mirage.resource.ram import RAMResource # noqa: E402
from mirage.workspace.session.state import seed_var # noqa: E402
from mirage.workspace.session.store import SessionStore # noqa: E402
from mirage.workspace.store.redis import RedisWorkspaceStateStore # noqa: E402
from mirage.workspace.store.s3 import S3WorkspaceStateStore # noqa: E402
@@ -89,7 +90,7 @@ async def write(prefix: str) -> None:
check("py write: symlink", result.exit_code == 0)
ws.create_session("narrow", mounts={"/data": "read"})
shared = ws.create_session("shared")
shared.env["ORIGIN"] = "py"
seed_var(shared, "ORIGIN", "py")
await ws.flush_sessions()
check("py write: shared session at generation 1", shared.generation == 1,
f"got {shared.generation}")
@@ -141,7 +142,7 @@ async def read(prefix: str) -> None:
check("py read: shared session hydrated",
shared.env.get("ORIGIN") == "ts" and base >= 1,
f"got env={shared.env!r} generation={base}")
shared.env["REPLY"] = "py"
seed_var(shared, "REPLY", "py")
await ws.flush_sessions()
sess_store = store.sessions(WORKSPACE_ID)
entries = await sess_store.load()
@@ -156,7 +157,7 @@ async def read(prefix: str) -> None:
ahead = dict(entries["shared"])
ahead["generation"] = base + 5
await sess_store.set("shared", ahead)
shared.env["AGAIN"] = "py"
seed_var(shared, "AGAIN", "py")
await ws.flush_sessions()
entries = await sess_store.load()
check(
+4 -3
View File
@@ -13,6 +13,7 @@
// ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
import { MountMode, RAMResource, type SessionStore } from '@struktoai/mirage-core'
import { seedVar } from '@struktoai/mirage-core/workspace/session/state'
import { RedisWorkspaceStateStore, S3WorkspaceStateStore, Workspace } from '@struktoai/mirage-node'
const REDIS_URL = process.env.REDIS_URL ?? 'redis://localhost:6379/0'
@@ -74,7 +75,7 @@ async function write(prefix: string): Promise<void> {
check('ts write: symlink', link.exitCode === 0)
ws.createSession('narrow', { mounts: { '/data': 'read' } })
const shared = ws.createSession('shared')
shared.env.ORIGIN = 'ts'
seedVar(shared, 'ORIGIN', 'ts')
await ws.flushSessions()
check(
'ts write: shared session at generation 1',
@@ -139,7 +140,7 @@ async function read(prefix: string): Promise<void> {
shared.env.ORIGIN === 'py' && base >= 1,
`got env=${JSON.stringify(shared.env)} generation=${String(base)}`,
)
shared.env.REPLY = 'ts'
seedVar(shared, 'REPLY', 'ts')
await ws.flushSessions()
const sessStore = store.sessions(WORKSPACE_ID)
const bumped = (await sessStore.load()).get('shared')
@@ -155,7 +156,7 @@ async function read(prefix: string): Promise<void> {
// A third writer advances the record behind our back; the next flush
// must adopt its generation and land serialized on top.
await sessStore.set('shared', { ...(bumped ?? {}), generation: base + 5 })
shared.env.AGAIN = 'ts'
seedVar(shared, 'AGAIN', 'ts')
await ws.flushSessions()
const final = (await sessStore.load()).get('shared')
const finalEnv = (final?.env ?? {}) as Record<string, string>
@@ -0,0 +1,85 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
import re
from pathlib import Path
REPO = Path(__file__).resolve().parents[4]
# `Session.env` / `.arrays` / `.readonly_vars` are read-only projections
# of the variable records (MappingProxyType in python, Object.freeze in
# TypeScript), so a write into one raises at runtime rather than landing.
# Writers go through `seed_var`/`seedVar`, `set_attr`/`setAttr`, or the
# `env` setter, which is what makes the pre_session gate unskippable.
#
# This is a meta-test rather than a type rule because the code that keeps
# getting it wrong is exactly the code no type checker sees: mypy runs on
# `mirage/` alone (`packages = ["mirage"]` in pyproject, and the hook is
# gated on `^python/mirage/`), so `integ/` and `tests/` are invisible to
# it, and that is where all of the last batch lived -- two runners and
# three state_store writes, none of which ran often enough to be caught
# by their own exception.
PY_PATTERNS = (
re.compile(r"\.env\.(update|pop|setdefault|clear)\s*\("),
re.compile(r"\.env\[[^\]]+\]\s*=[^=]"),
re.compile(r"\.arrays\.(update|pop|setdefault|clear)\s*\("),
re.compile(r"\.arrays\[[^\]]+\]\s*=[^=]"),
)
TS_PATTERNS = (
re.compile(r"Object\.assign\(\s*[A-Za-z_][\w.]*\.(env|arrays)\b"),
re.compile(r"(?<!process)\.env\.[A-Za-z_]\w*\s*=[^=]"),
re.compile(r"(?<!process)\.env\[[^\]]+\]\s*=[^=]"),
)
# `CommandOpts.env` is a plain dict copy handed to one command
# invocation (`env_snapshot` returns a fresh dict), so writing into it is
# writing into a throwaway and is exactly what these two tests assert.
ALLOWED = {
"python/tests/workspace/test_state_doors.py",
"typescript/packages/core/src/workspace/state_doors.test.ts",
}
SEARCH_ROOTS = ("python/mirage", "python/tests", "integ", "examples",
"typescript/packages")
def _sources() -> list[Path]:
out: list[Path] = []
for root in SEARCH_ROOTS:
base = REPO / root
if not base.is_dir():
continue
for path in base.rglob("*"):
if path.suffix not in (".py", ".ts"):
continue
parts = set(path.parts)
if parts & {"node_modules", "dist", ".venv", "__pycache__"}:
continue
out.append(path)
return out
def test_no_writes_into_the_read_only_projections() -> None:
offenders: list[str] = []
for path in _sources():
rel = path.relative_to(REPO).as_posix()
if rel in ALLOWED:
continue
patterns = PY_PATTERNS if path.suffix == ".py" else TS_PATTERNS
for i, line in enumerate(path.read_text().splitlines(), 1):
if any(p.search(line) for p in patterns):
offenders.append(f"{rel}:{i}: {line.strip()}")
assert not offenders, ("write into a read-only session projection; use "
"seed_var/seedVar, set_attr/setAttr or the `env` "
"setter instead:\n" + "\n".join(offenders))