Compare commits
1 Commits
fix/808-ag
...
fix/807-gl
| Author | SHA1 | Date | |
|---|---|---|---|
| e1d3891bd1 |
64
docs/scratchpads/807-glpi-partial-content.md
Normal file
64
docs/scratchpads/807-glpi-partial-content.md
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
# Issue #807 — GLPI list wrappers accept HTTP 206
|
||||||
|
|
||||||
|
- **Branch:** `fix/807-glpi-206`
|
||||||
|
- **Task:** Gitea issue #807
|
||||||
|
- **Role:** Author-only worker reporting to `mosaic-100`; no self-review or merge
|
||||||
|
- **Started:** 2026-07-16
|
||||||
|
|
||||||
|
## Objective
|
||||||
|
|
||||||
|
Fix the shipped GLPI ticket, computer, and user list wrappers so ranged responses with HTTP 206 Partial Content render successfully while genuine HTTP failures remain non-zero errors.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
- Modify only the three affected list wrappers and a focused shell regression test.
|
||||||
|
- Do not touch `session-init.sh`, `ticket-create.sh`, or `docs/TASKS.md`.
|
||||||
|
- Add task-local delivery evidence here as required by the mission protocol.
|
||||||
|
|
||||||
|
## Plan
|
||||||
|
|
||||||
|
1. Add a deterministic shell harness that copies each wrapper beside stubbed `session-init.sh`, credentials, and `curl` boundaries.
|
||||||
|
2. Prove RED against the current 200-only gates: 206 must fail before the implementation change.
|
||||||
|
3. Update all three status gates to accept exactly 200 or 206.
|
||||||
|
4. Prove GREEN for 206 rendering and genuine 401/500 failures, then run repository quality gates.
|
||||||
|
5. Commit with co-author attribution, run the push queue guard, push, and open a PR for independent review and merge by the team lead.
|
||||||
|
|
||||||
|
## Budget
|
||||||
|
|
||||||
|
- No explicit token cap supplied.
|
||||||
|
- Soft estimate: 8K tokens; narrow single-worker execution with no exploratory scope.
|
||||||
|
|
||||||
|
## Progress
|
||||||
|
|
||||||
|
- [x] Mission, task, PRD, QA, documentation, and code-review guidance loaded.
|
||||||
|
- [x] RED regression evidence captured: `test-list-http-status.sh` exited 1; all three wrappers rejected 206 while retaining 401 failures.
|
||||||
|
- [x] Implementation complete.
|
||||||
|
- [x] Relevant tests and repository gates green.
|
||||||
|
- [ ] Commit pushed and PR opened.
|
||||||
|
|
||||||
|
## Tests and evidence
|
||||||
|
|
||||||
|
- RED (before source fix): `packages/mosaic/framework/tools/glpi/test-list-http-status.sh` → exit 1; ticket/computer/user 206 assertions failed, all 401 assertions passed.
|
||||||
|
- GREEN: `bash -n packages/mosaic/framework/tools/glpi/{ticket-list.sh,computer-list.sh,user-list.sh,test-list-http-status.sh}` → pass.
|
||||||
|
- GREEN: `shellcheck packages/mosaic/framework/tools/glpi/test-list-http-status.sh` → pass.
|
||||||
|
- GREEN: `packages/mosaic/framework/tools/glpi/test-list-http-status.sh` → 6 assertions pass (206 renders and 401 errors for all three wrappers).
|
||||||
|
- GREEN: `pnpm typecheck` → 42/42 tasks pass.
|
||||||
|
- GREEN: `pnpm lint` → 23/23 tasks pass.
|
||||||
|
- GREEN: `pnpm format:check` → all matched files pass.
|
||||||
|
- Setup note: initial gate attempts could not start because the fresh worktree lacked dependencies; `pnpm install --frozen-lockfile --store-dir /home/hermes/.local/share/pnpm/store` restored the locked workspace dependencies without lockfile changes.
|
||||||
|
|
||||||
|
## Acceptance criteria mapping
|
||||||
|
|
||||||
|
| Criterion | Evidence |
|
||||||
|
| --- | --- |
|
||||||
|
| HTTP 206 succeeds and renders each ranged list | Focused test's three 206 render assertions pass |
|
||||||
|
| Genuine HTTP failures remain non-zero with existing diagnostics | Focused test's three HTTP 401 assertions pass |
|
||||||
|
| Only affected list wrappers change | Diff contains the three status predicates plus focused test/evidence; session and create wrappers untouched |
|
||||||
|
|
||||||
|
## Documentation decision
|
||||||
|
|
||||||
|
No operator/API documentation change is needed: this restores documented list behavior for a healthy GLPI response without changing command syntax, output, configuration, or public contracts. This task scratchpad records delivery evidence.
|
||||||
|
|
||||||
|
## Risks / blockers
|
||||||
|
|
||||||
|
- Existing dirty `.mosaic/orchestrator/mission.json` and `.mosaic/orchestrator/session.lock` are runtime-owned and will not be edited or committed.
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
# Issue #808 — agent-send sender identity
|
|
||||||
|
|
||||||
## Objective
|
|
||||||
|
|
||||||
Fix cross-socket `agent-send.sh` preambles so replies route to the real sender rather than a destination-socket holder session.
|
|
||||||
|
|
||||||
## Scope and acceptance criteria
|
|
||||||
|
|
||||||
- Prefer exported `MOSAIC_AGENT_NAME` as the authoritative sender session name.
|
|
||||||
- If it is unset, query the sender's local/default tmux socket for `#S` without destination `-L` arguments.
|
|
||||||
- Preserve `?` when sender identity cannot be determined.
|
|
||||||
- Do not alter destination socket dispatch.
|
|
||||||
- Add red-first regressions for all three identity paths.
|
|
||||||
|
|
||||||
## Plan
|
|
||||||
|
|
||||||
1. Extend `agent-send.test.sh` with deterministic fake-tmux coverage.
|
|
||||||
2. Run the test against the unpatched implementation and record RED evidence.
|
|
||||||
3. Apply the minimal sender lookup fix only.
|
|
||||||
4. Run the focused suite and repository quality gates.
|
|
||||||
5. Commit, queue-guard, push, and open an author-only PR for independent review.
|
|
||||||
|
|
||||||
## Constraints and risks
|
|
||||||
|
|
||||||
- Worker lane is author-only: no self-review or merge.
|
|
||||||
- `docs/TASKS.md` and mission state are orchestrator-owned and will not be modified.
|
|
||||||
- Pre-existing runtime changes under `.mosaic/orchestrator/` are excluded from this work.
|
|
||||||
- Budget: no explicit token cap; keep changes limited to the shell tool, sibling regression test, and this scratchpad.
|
|
||||||
|
|
||||||
## Evidence
|
|
||||||
|
|
||||||
- RED: `bash packages/mosaic/framework/tools/tmux/agent-send.test.sh` failed on the unpatched implementation with `PASS=12 FAIL=3`; it selected `destination-holder` instead of both `MOSAIC_AGENT_NAME=authoritative-agent` and local session `local-agent`. The genuinely unavailable sender case already exercised and preserved `?`.
|
|
||||||
- GREEN: `bash packages/mosaic/framework/tools/tmux/agent-send.test.sh` passed with `PASS=15 FAIL=0`; coverage includes env authority, local/default tmux fallback across a destination `-L`, explicit rejection of the destination holder, and `?` fallback.
|
|
||||||
- Syntax: `bash -n packages/mosaic/framework/tools/tmux/agent-send.sh packages/mosaic/framework/tools/tmux/agent-send.test.sh` passed.
|
|
||||||
- Quality gates: `pnpm typecheck` (42/42 tasks), `pnpm lint` (23/23 tasks), and `pnpm format:check` all passed after installing the frozen lockfile dependencies. The first install attempt failed because pnpm's configured store pointed at `/root`; retrying with the existing user-owned store (`--store-dir /home/hermes/.local/share/pnpm/store`) succeeded without changing tracked dependency files.
|
|
||||||
- Documentation: no public API or operator workflow changed; the source comment, regression-test contract, and this implementation record cover the internal bug fix.
|
|
||||||
- Independent review: intentionally pending for the reviewer assigned by `mosaic-100`; this author-only lane will not self-review or merge.
|
|
||||||
@@ -37,7 +37,7 @@ response=$(curl -sk -w "\n%{http_code}" \
|
|||||||
http_code=$(echo "$response" | tail -n1)
|
http_code=$(echo "$response" | tail -n1)
|
||||||
body=$(echo "$response" | sed '$d')
|
body=$(echo "$response" | sed '$d')
|
||||||
|
|
||||||
if [[ "$http_code" != "200" ]]; then
|
if [[ "$http_code" != "200" && "$http_code" != "206" ]]; then
|
||||||
echo "Error: Failed to list computers (HTTP $http_code)" >&2
|
echo "Error: Failed to list computers (HTTP $http_code)" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
84
packages/mosaic/framework/tools/glpi/test-list-http-status.sh
Executable file
84
packages/mosaic/framework/tools/glpi/test-list-http-status.sh
Executable file
@@ -0,0 +1,84 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Regression harness for #807: ranged GLPI list requests may return HTTP 206.
|
||||||
|
#
|
||||||
|
# Each shipped list wrapper must render a healthy 206 response and must retain
|
||||||
|
# its non-zero error behavior for a genuine HTTP failure.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
WORK_DIR="${MOSAIC_TEST_WORK_DIR:-$PWD/.mosaic-test-work/glpi-list-http-status}"
|
||||||
|
TOOL_DIR="$WORK_DIR/tools/glpi"
|
||||||
|
BIN_DIR="$WORK_DIR/bin"
|
||||||
|
|
||||||
|
rm -rf "$WORK_DIR"
|
||||||
|
mkdir -p "$TOOL_DIR" "$BIN_DIR" "$WORK_DIR/tools/_lib"
|
||||||
|
trap 'rm -rf "$WORK_DIR"' EXIT
|
||||||
|
|
||||||
|
for wrapper in ticket-list.sh computer-list.sh user-list.sh; do
|
||||||
|
cp "$SCRIPT_DIR/$wrapper" "$TOOL_DIR/$wrapper"
|
||||||
|
done
|
||||||
|
|
||||||
|
cat > "$WORK_DIR/tools/_lib/credentials.sh" <<'SH'
|
||||||
|
load_credentials() {
|
||||||
|
export GLPI_URL="https://glpi.test/apirest.php"
|
||||||
|
export GLPI_APP_TOKEN="test-app-token"
|
||||||
|
}
|
||||||
|
SH
|
||||||
|
|
||||||
|
cat > "$TOOL_DIR/session-init.sh" <<'SH'
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
printf '%s\n' 'test-session-token'
|
||||||
|
SH
|
||||||
|
chmod +x "$TOOL_DIR/session-init.sh"
|
||||||
|
|
||||||
|
cat > "$BIN_DIR/curl" <<'SH'
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
printf '%s\n' '[{"id":42,"priority":3,"status":1,"name":"Regression fixture","date_mod":"2026-07-16 10:00:00","serial":"SER-42","states_id":1,"realname":"Fixture","firstname":"GLPI","is_active":1}]'
|
||||||
|
printf '%s\n' "${GLPI_TEST_HTTP_CODE:?GLPI_TEST_HTTP_CODE is required}"
|
||||||
|
SH
|
||||||
|
chmod +x "$BIN_DIR/curl"
|
||||||
|
|
||||||
|
export MOSAIC_HOME="$WORK_DIR"
|
||||||
|
export PATH="$BIN_DIR:$PATH"
|
||||||
|
|
||||||
|
wrappers=(ticket-list.sh computer-list.sh user-list.sh)
|
||||||
|
resources=(tickets computers users)
|
||||||
|
headings=(PRIORITY SERIAL USERNAME)
|
||||||
|
fail=0
|
||||||
|
|
||||||
|
for index in "${!wrappers[@]}"; do
|
||||||
|
wrapper="${wrappers[$index]}"
|
||||||
|
resource="${resources[$index]}"
|
||||||
|
heading="${headings[$index]}"
|
||||||
|
path="$TOOL_DIR/$wrapper"
|
||||||
|
|
||||||
|
if ! output=$(GLPI_TEST_HTTP_CODE=206 bash "$path" 2>&1); then
|
||||||
|
echo "FAIL: $wrapper rejected healthy HTTP 206" >&2
|
||||||
|
fail=1
|
||||||
|
elif [[ "$output" != *"$heading"* || "$output" != *"Regression fixture"* ]]; then
|
||||||
|
echo "FAIL: $wrapper did not render the HTTP 206 list response" >&2
|
||||||
|
fail=1
|
||||||
|
else
|
||||||
|
echo "PASS: $wrapper renders HTTP 206"
|
||||||
|
fi
|
||||||
|
|
||||||
|
rc=0
|
||||||
|
output=$(GLPI_TEST_HTTP_CODE=401 bash "$path" 2>&1) || rc=$?
|
||||||
|
if [[ "$rc" -eq 0 ]]; then
|
||||||
|
echo "FAIL: $wrapper accepted HTTP 401" >&2
|
||||||
|
fail=1
|
||||||
|
elif [[ "$output" != *"Error: Failed to list $resource (HTTP 401)"* ]]; then
|
||||||
|
echo "FAIL: $wrapper changed the HTTP failure diagnostic" >&2
|
||||||
|
fail=1
|
||||||
|
else
|
||||||
|
echo "PASS: $wrapper rejects HTTP 401"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ "$fail" -eq 0 ]]; then
|
||||||
|
echo "ALL PASS: test-list-http-status.sh"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit "$fail"
|
||||||
@@ -55,7 +55,7 @@ response=$(curl -sk -w "\n%{http_code}" \
|
|||||||
http_code=$(echo "$response" | tail -n1)
|
http_code=$(echo "$response" | tail -n1)
|
||||||
body=$(echo "$response" | sed '$d')
|
body=$(echo "$response" | sed '$d')
|
||||||
|
|
||||||
if [[ "$http_code" != "200" ]]; then
|
if [[ "$http_code" != "200" && "$http_code" != "206" ]]; then
|
||||||
echo "Error: Failed to list tickets (HTTP $http_code)" >&2
|
echo "Error: Failed to list tickets (HTTP $http_code)" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ response=$(curl -sk -w "\n%{http_code}" \
|
|||||||
http_code=$(echo "$response" | tail -n1)
|
http_code=$(echo "$response" | tail -n1)
|
||||||
body=$(echo "$response" | sed '$d')
|
body=$(echo "$response" | sed '$d')
|
||||||
|
|
||||||
if [[ "$http_code" != "200" ]]; then
|
if [[ "$http_code" != "200" && "$http_code" != "206" ]]; then
|
||||||
echo "Error: Failed to list users (HTTP $http_code)" >&2
|
echo "Error: Failed to list users (HTTP $http_code)" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -122,11 +122,12 @@ fi
|
|||||||
|
|
||||||
# Source label: this agent's host:session (auto-detected, overridable).
|
# Source label: this agent's host:session (auto-detected, overridable).
|
||||||
if [ -z "$SRC_LABEL" ]; then
|
if [ -z "$SRC_LABEL" ]; then
|
||||||
src_host=$(hostname -s 2>/dev/null || echo "?")
|
tmux_cmd=(tmux)
|
||||||
src_sess=${MOSAIC_AGENT_NAME:-}
|
if [ -n "$SOCKET_NAME" ]; then
|
||||||
if [ -z "$src_sess" ]; then
|
tmux_cmd+=(-L "$SOCKET_NAME")
|
||||||
src_sess=$(tmux display-message -p '#S' 2>/dev/null || echo "?")
|
|
||||||
fi
|
fi
|
||||||
|
src_host=$(hostname -s 2>/dev/null || echo "?")
|
||||||
|
src_sess=$("${tmux_cmd[@]}" display-message -p '#S' 2>/dev/null || echo "?")
|
||||||
SRC_LABEL="${src_host}:${src_sess}"
|
SRC_LABEL="${src_host}:${src_sess}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -14,9 +14,6 @@
|
|||||||
# 5. invalid class => exit 3, nothing sent.
|
# 5. invalid class => exit 3, nothing sent.
|
||||||
# 6. --class with no value => exit 3.
|
# 6. --class with no value => exit 3.
|
||||||
# 7. the documented consumer regex parses producer output for every class.
|
# 7. the documented consumer regex parses producer output for every class.
|
||||||
# 8. MOSAIC_AGENT_NAME is authoritative for sender identity.
|
|
||||||
# 9. sender fallback queries local tmux, never the destination -L socket.
|
|
||||||
# 10. an undeterminable sender is stamped as "?".
|
|
||||||
set -uo pipefail
|
set -uo pipefail
|
||||||
|
|
||||||
HERE=$(cd -- "$(dirname -- "$0")" && pwd)
|
HERE=$(cd -- "$(dirname -- "$0")" && pwd)
|
||||||
@@ -24,45 +21,22 @@ TOOL="$HERE/agent-send.sh"
|
|||||||
|
|
||||||
# Capture stub: stands in for send-message.sh. Decodes -b and prints the payload.
|
# Capture stub: stands in for send-message.sh. Decodes -b and prints the payload.
|
||||||
STUB=$(mktemp)
|
STUB=$(mktemp)
|
||||||
FAKE_BIN=$(mktemp -d)
|
trap 'rm -f "$STUB"' EXIT
|
||||||
trap 'rm -f "$STUB"; rm -rf "$FAKE_BIN"' EXIT
|
|
||||||
cat >"$STUB" <<'STUB_EOF'
|
cat >"$STUB" <<'STUB_EOF'
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -uo pipefail
|
set -uo pipefail
|
||||||
b64=""
|
b64=""
|
||||||
while getopts "L:t:b:r:v" o; do case "$o" in b) b64=$OPTARG ;; *) : ;; esac; done
|
while getopts "t:b:r:v" o; do case "$o" in b) b64=$OPTARG ;; *) : ;; esac; done
|
||||||
printf '%s' "$b64" | base64 -d
|
printf '%s' "$b64" | base64 -d
|
||||||
STUB_EOF
|
STUB_EOF
|
||||||
chmod +x "$STUB"
|
chmod +x "$STUB"
|
||||||
|
|
||||||
# Fake tmux distinguishes the sender's default socket from a destination socket.
|
|
||||||
cat >"$FAKE_BIN/tmux" <<'TMUX_EOF'
|
|
||||||
#!/usr/bin/env bash
|
|
||||||
set -uo pipefail
|
|
||||||
case "${FAKE_TMUX_MODE:-sessions}" in
|
|
||||||
unavailable) exit 1 ;;
|
|
||||||
sessions)
|
|
||||||
if [ "${1:-}" = "-L" ]; then
|
|
||||||
printf '%s\n' 'destination-holder'
|
|
||||||
else
|
|
||||||
printf '%s\n' 'local-agent'
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
TMUX_EOF
|
|
||||||
chmod +x "$FAKE_BIN/tmux"
|
|
||||||
|
|
||||||
PASS=0; FAIL=0
|
PASS=0; FAIL=0
|
||||||
ok() { PASS=$((PASS+1)); printf 'ok %s\n' "$1"; }
|
ok() { PASS=$((PASS+1)); printf 'ok %s\n' "$1"; }
|
||||||
no() { FAIL=$((FAIL+1)); printf 'FAIL %s\n %s\n' "$1" "$2"; }
|
no() { FAIL=$((FAIL+1)); printf 'FAIL %s\n %s\n' "$1" "$2"; }
|
||||||
|
|
||||||
# Run the tool with the stub injected; echoes captured payload on stdout.
|
# Run the tool with the stub injected; echoes captured payload on stdout.
|
||||||
run() { AGENT_SEND_SENDER="$STUB" bash "$TOOL" -S a:src -n dsthost "$@"; }
|
run() { AGENT_SEND_SENDER="$STUB" bash "$TOOL" -S a:src -n dsthost "$@"; }
|
||||||
run_auto() {
|
|
||||||
env -u MOSAIC_AGENT_NAME \
|
|
||||||
AGENT_SEND_SENDER="$STUB" PATH="$FAKE_BIN:$PATH" \
|
|
||||||
bash "$TOOL" -n dsthost "$@"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Documented consumer grammar — the daemon will mirror exactly this.
|
# Documented consumer grammar — the daemon will mirror exactly this.
|
||||||
GRAMMAR='^\[(\S+) -> (\S+) class=(terminal-log|actionable|human|reaction)\] (.*)$'
|
GRAMMAR='^\[(\S+) -> (\S+) class=(terminal-log|actionable|human|reaction)\] (.*)$'
|
||||||
@@ -118,30 +92,6 @@ classic=$(run -s mos -m "plain body")
|
|||||||
[[ "$classic" =~ $GRAMMAR_NOCLASS ]] && [ "${BASH_REMATCH[3]}" = "plain body" ] \
|
[[ "$classic" =~ $GRAMMAR_NOCLASS ]] && [ "${BASH_REMATCH[3]}" = "plain body" ] \
|
||||||
&& ok "grammar (no-class) parses classic line" || no "grammar (no-class) parses classic line" "line=[$classic]"
|
&& ok "grammar (no-class) parses classic line" || no "grammar (no-class) parses classic line" "line=[$classic]"
|
||||||
|
|
||||||
# 8. Exported pane identity wins even when dispatch targets another tmux socket.
|
|
||||||
src_host=$(hostname -s)
|
|
||||||
got=$(MOSAIC_AGENT_NAME=authoritative-agent FAKE_TMUX_MODE=sessions \
|
|
||||||
AGENT_SEND_SENDER="$STUB" PATH="$FAKE_BIN:$PATH" \
|
|
||||||
bash "$TOOL" -L destination-socket -n dsthost -s mos -m "env identity")
|
|
||||||
want="[$src_host:authoritative-agent -> dsthost:mos] env identity"
|
|
||||||
[ "$got" = "$want" ] && ok "MOSAIC_AGENT_NAME is authoritative across sockets" \
|
|
||||||
|| no "MOSAIC_AGENT_NAME is authoritative across sockets" "got=[$got] want=[$want]"
|
|
||||||
|
|
||||||
# 9. Without the env identity, self-lookup uses local tmux, not destination -L.
|
|
||||||
got=$(FAKE_TMUX_MODE=sessions run_auto -L destination-socket -s mos -m "local fallback")
|
|
||||||
want="[$src_host:local-agent -> dsthost:mos] local fallback"
|
|
||||||
[ "$got" = "$want" ] && ok "cross-socket fallback uses local sender session" \
|
|
||||||
|| no "cross-socket fallback uses local sender session" "got=[$got] want=[$want]"
|
|
||||||
[[ "$got" != *":destination-holder ->"* ]] \
|
|
||||||
&& ok "cross-socket fallback rejects destination holder identity" \
|
|
||||||
|| no "cross-socket fallback rejects destination holder identity" "got=[$got]"
|
|
||||||
|
|
||||||
# 10. If neither env nor local tmux identifies the sender, preserve '?'.
|
|
||||||
got=$(FAKE_TMUX_MODE=unavailable run_auto -L destination-socket -s mos -m "unknown fallback")
|
|
||||||
want="[$src_host:? -> dsthost:mos] unknown fallback"
|
|
||||||
[ "$got" = "$want" ] && ok "unknown sender falls back to ?" \
|
|
||||||
|| no "unknown sender falls back to ?" "got=[$got] want=[$want]"
|
|
||||||
|
|
||||||
echo "---"
|
echo "---"
|
||||||
echo "PASS=$PASS FAIL=$FAIL"
|
echo "PASS=$PASS FAIL=$FAIL"
|
||||||
[ "$FAIL" -eq 0 ]
|
[ "$FAIL" -eq 0 ]
|
||||||
|
|||||||
Reference in New Issue
Block a user