mirror of
https://github.com/tw93/Mole.git
synced 2026-03-22 18:30:08 +00:00
feat: add JSON output tests and README docs for analyze and status (#556)
* feat: add JSON output tests and README docs for analyze and status Add 7 BATS tests covering `--json` output for `mo analyze` and `mo status`: - schema structure - field types - pipe auto-detection. Also document the `--json` flag in a new "Machine-Readable Output" README section, including the auto-detection behavior when piped. * chore: use waitgroup go in status collector --------- Co-authored-by: Tw93 <hitw93@gmail.com>
This commit is contained in:
36
README.md
36
README.md
@@ -210,6 +210,42 @@ Health score is based on CPU, memory, disk, temperature, and I/O load, with colo
|
||||
|
||||
Shortcuts: In `mo status`, press `k` to toggle the cat and save the preference, and `q` to quit.
|
||||
|
||||
#### Machine-Readable Output
|
||||
|
||||
Both `mo analyze` and `mo status` support a `--json` flag for scripting and automation.
|
||||
|
||||
`mo status` also auto-detects when its output is piped (not a terminal) and switches to JSON automatically.
|
||||
|
||||
```bash
|
||||
# Disk analysis as JSON
|
||||
$ mo analyze --json ~/Documents
|
||||
{
|
||||
"path": "/Users/you/Documents",
|
||||
"entries": [
|
||||
{ "name": "Library", "path": "...", "size": 80939438080, "is_dir": true },
|
||||
...
|
||||
],
|
||||
"total_size": 168393441280,
|
||||
"total_files": 42187
|
||||
}
|
||||
|
||||
# System status as JSON
|
||||
$ mo status --json
|
||||
{
|
||||
"host": "MacBook-Pro",
|
||||
"health_score": 92,
|
||||
"cpu": { "usage": 45.2, "logical_cpu": 8, ... },
|
||||
"memory": { "total": 25769803776, "used": 15049334784, "used_percent": 58.4 },
|
||||
"disks": [ ... ],
|
||||
"uptime": "3d 12h 45m",
|
||||
...
|
||||
}
|
||||
|
||||
# Auto-detected JSON when piped
|
||||
$ mo status | jq '.health_score'
|
||||
92
|
||||
```
|
||||
|
||||
### Project Artifact Purge
|
||||
|
||||
Clean old build artifacts such as `node_modules`, `target`, `build`, and `dist` to free up disk space.
|
||||
|
||||
@@ -255,9 +255,7 @@ func (c *Collector) Collect() (MetricsSnapshot, error) {
|
||||
|
||||
// Helper to launch concurrent collection.
|
||||
collect := func(fn func() error) {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
wg.Go(func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
errMu.Lock()
|
||||
@@ -279,7 +277,7 @@ func (c *Collector) Collect() (MetricsSnapshot, error) {
|
||||
}
|
||||
errMu.Unlock()
|
||||
}
|
||||
}()
|
||||
})
|
||||
}
|
||||
|
||||
// Launch independent collection tasks.
|
||||
|
||||
141
tests/cli.bats
141
tests/cli.bats
@@ -11,6 +11,19 @@ setup_file() {
|
||||
export HOME
|
||||
|
||||
mkdir -p "$HOME"
|
||||
|
||||
# Build Go binaries from current source for JSON tests.
|
||||
# Point GOPATH/GOMODCACHE at the real home so go build doesn't write
|
||||
# module caches into the fake HOME under tests/.
|
||||
if command -v go > /dev/null 2>&1; then
|
||||
ANALYZE_BIN="$(mktemp "${TMPDIR:-/tmp}/analyze-go.XXXXXX")"
|
||||
STATUS_BIN="$(mktemp "${TMPDIR:-/tmp}/status-go.XXXXXX")"
|
||||
GOPATH="${ORIGINAL_HOME}/go" GOMODCACHE="${ORIGINAL_HOME}/go/pkg/mod" \
|
||||
go build -o "$ANALYZE_BIN" "$PROJECT_ROOT/cmd/analyze" 2>/dev/null
|
||||
GOPATH="${ORIGINAL_HOME}/go" GOMODCACHE="${ORIGINAL_HOME}/go/pkg/mod" \
|
||||
go build -o "$STATUS_BIN" "$PROJECT_ROOT/cmd/status" 2>/dev/null
|
||||
export ANALYZE_BIN STATUS_BIN
|
||||
fi
|
||||
}
|
||||
|
||||
teardown_file() {
|
||||
@@ -19,6 +32,7 @@ teardown_file() {
|
||||
if [[ -n "${ORIGINAL_HOME:-}" ]]; then
|
||||
export HOME="$ORIGINAL_HOME"
|
||||
fi
|
||||
rm -f "${ANALYZE_BIN:-}" "${STATUS_BIN:-}"
|
||||
}
|
||||
|
||||
create_fake_utils() {
|
||||
@@ -278,3 +292,130 @@ EOF
|
||||
run grep "pam_tid.so" "$pam_file"
|
||||
[ "$status" -ne 0 ]
|
||||
}
|
||||
|
||||
# --- JSON output mode tests ---
|
||||
|
||||
@test "mo analyze --json outputs valid JSON with expected fields" {
|
||||
if [[ ! -x "${ANALYZE_BIN:-}" ]]; then
|
||||
skip "analyze binary not available (go not installed?)"
|
||||
fi
|
||||
|
||||
run "$ANALYZE_BIN" --json /tmp
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
# Validate it is parseable JSON
|
||||
echo "$output" | python3 -c "import sys, json; json.load(sys.stdin)"
|
||||
|
||||
# Check required top-level keys
|
||||
echo "$output" | python3 -c "
|
||||
import sys, json
|
||||
data = json.load(sys.stdin)
|
||||
assert 'path' in data, 'missing path'
|
||||
assert 'entries' in data, 'missing entries'
|
||||
assert 'total_size' in data, 'missing total_size'
|
||||
assert 'total_files' in data, 'missing total_files'
|
||||
assert isinstance(data['entries'], list), 'entries is not a list'
|
||||
"
|
||||
}
|
||||
|
||||
@test "mo analyze --json entries contain required fields" {
|
||||
if [[ ! -x "${ANALYZE_BIN:-}" ]]; then
|
||||
skip "analyze binary not available (go not installed?)"
|
||||
fi
|
||||
|
||||
run "$ANALYZE_BIN" --json /tmp
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
echo "$output" | python3 -c "
|
||||
import sys, json
|
||||
data = json.load(sys.stdin)
|
||||
for entry in data['entries']:
|
||||
assert 'name' in entry, 'entry missing name'
|
||||
assert 'path' in entry, 'entry missing path'
|
||||
assert 'size' in entry, 'entry missing size'
|
||||
assert 'is_dir' in entry, 'entry missing is_dir'
|
||||
"
|
||||
}
|
||||
|
||||
@test "mo analyze --json path reflects target directory" {
|
||||
if [[ ! -x "${ANALYZE_BIN:-}" ]]; then
|
||||
skip "analyze binary not available (go not installed?)"
|
||||
fi
|
||||
|
||||
run "$ANALYZE_BIN" --json /tmp
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
echo "$output" | python3 -c "
|
||||
import sys, json
|
||||
data = json.load(sys.stdin)
|
||||
assert data['path'] == '/tmp' or data['path'] == '/private/tmp', \
|
||||
f\"unexpected path: {data['path']}\"
|
||||
"
|
||||
}
|
||||
|
||||
@test "mo status --json outputs valid JSON with expected fields" {
|
||||
if [[ ! -x "${STATUS_BIN:-}" ]]; then
|
||||
skip "status binary not available (go not installed?)"
|
||||
fi
|
||||
|
||||
run "$STATUS_BIN" --json
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
# Validate it is parseable JSON
|
||||
echo "$output" | python3 -c "import sys, json; json.load(sys.stdin)"
|
||||
|
||||
# Check required top-level keys
|
||||
echo "$output" | python3 -c "
|
||||
import sys, json
|
||||
data = json.load(sys.stdin)
|
||||
for key in ['cpu', 'memory', 'disks', 'health_score', 'host', 'uptime']:
|
||||
assert key in data, f'missing key: {key}'
|
||||
"
|
||||
}
|
||||
|
||||
@test "mo status --json cpu section has expected structure" {
|
||||
if [[ ! -x "${STATUS_BIN:-}" ]]; then
|
||||
skip "status binary not available (go not installed?)"
|
||||
fi
|
||||
|
||||
run "$STATUS_BIN" --json
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
echo "$output" | python3 -c "
|
||||
import sys, json
|
||||
data = json.load(sys.stdin)
|
||||
cpu = data['cpu']
|
||||
assert 'usage' in cpu, 'cpu missing usage'
|
||||
assert 'logical_cpu' in cpu, 'cpu missing logical_cpu'
|
||||
assert isinstance(cpu['usage'], (int, float)), 'cpu usage is not a number'
|
||||
"
|
||||
}
|
||||
|
||||
@test "mo status --json memory section has expected structure" {
|
||||
if [[ ! -x "${STATUS_BIN:-}" ]]; then
|
||||
skip "status binary not available (go not installed?)"
|
||||
fi
|
||||
|
||||
run "$STATUS_BIN" --json
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
echo "$output" | python3 -c "
|
||||
import sys, json
|
||||
data = json.load(sys.stdin)
|
||||
mem = data['memory']
|
||||
assert 'total' in mem, 'memory missing total'
|
||||
assert 'used' in mem, 'memory missing used'
|
||||
assert 'used_percent' in mem, 'memory missing used_percent'
|
||||
assert mem['total'] > 0, 'memory total should be positive'
|
||||
"
|
||||
}
|
||||
|
||||
@test "mo status --json piped to stdout auto-detects JSON mode" {
|
||||
if [[ ! -x "${STATUS_BIN:-}" ]]; then
|
||||
skip "status binary not available (go not installed?)"
|
||||
fi
|
||||
|
||||
# When piped (not a tty), status should auto-detect and output JSON
|
||||
output=$("$STATUS_BIN" 2>/dev/null)
|
||||
echo "$output" | python3 -c "import sys, json; json.load(sys.stdin)"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user