diff --git a/bin/analyze-go b/bin/analyze-go index 7dd7d2e..006f53d 100755 Binary files a/bin/analyze-go and b/bin/analyze-go differ diff --git a/bin/status-go b/bin/status-go index c76c885..b4deee2 100755 Binary files a/bin/status-go and b/bin/status-go differ diff --git a/bin/touchid.sh b/bin/touchid.sh index 1e7da7f..e66b531 100755 --- a/bin/touchid.sh +++ b/bin/touchid.sh @@ -58,6 +58,10 @@ show_status() { # Enable Touch ID for sudo enable_touchid() { + # Cleanup trap + local temp_file="" + trap '[[ -n "$temp_file" ]] && rm -f "$temp_file"' EXIT + # First check if system supports Touch ID if ! supports_touchid; then log_warning "This Mac may not support Touch ID" @@ -75,14 +79,15 @@ enable_touchid() { return 0 fi - # Create backup and apply changes - if ! sudo cp "$PAM_SUDO_FILE" "${PAM_SUDO_FILE}.mole-backup" 2> /dev/null; then - log_error "Failed to create backup" - return 1 + # Create backup only if it doesn't exist to preserve original state + if [[ ! -f "${PAM_SUDO_FILE}.mole-backup" ]]; then + if ! sudo cp "$PAM_SUDO_FILE" "${PAM_SUDO_FILE}.mole-backup" 2> /dev/null; then + log_error "Failed to create backup" + return 1 + fi fi - # Create temp file with the modification - local temp_file + # Create temp file temp_file=$(mktemp) # Insert pam_tid.so after the first comment block @@ -96,13 +101,18 @@ enable_touchid() { { print } ' "$PAM_SUDO_FILE" > "$temp_file" + # Verify content change + if cmp -s "$PAM_SUDO_FILE" "$temp_file"; then + log_error "Failed to modify configuration" + return 1 + fi + # Apply the changes if sudo mv "$temp_file" "$PAM_SUDO_FILE" 2> /dev/null; then echo -e "${GREEN}${ICON_SUCCESS} Touch ID enabled${NC} ${GRAY}- try: sudo ls${NC}" echo "" return 0 else - rm -f "$temp_file" 2> /dev/null || true log_error "Failed to enable Touch ID" return 1 fi @@ -110,19 +120,24 @@ enable_touchid() { # Disable Touch ID for sudo disable_touchid() { + # Cleanup trap + local temp_file="" + trap '[[ -n "$temp_file" ]] && rm -f "$temp_file"' EXIT + if ! is_touchid_configured; then echo -e "${YELLOW}Touch ID is not currently enabled${NC}" return 0 fi - # Create backup and remove configuration - if ! sudo cp "$PAM_SUDO_FILE" "${PAM_SUDO_FILE}.mole-backup" 2> /dev/null; then - log_error "Failed to create backup" - return 1 + # Create backup only if it doesn't exist + if [[ ! -f "${PAM_SUDO_FILE}.mole-backup" ]]; then + if ! sudo cp "$PAM_SUDO_FILE" "${PAM_SUDO_FILE}.mole-backup" 2> /dev/null; then + log_error "Failed to create backup" + return 1 + fi fi # Remove pam_tid.so line - local temp_file temp_file=$(mktemp) grep -v "pam_tid.so" "$PAM_SUDO_FILE" > "$temp_file" @@ -131,7 +146,6 @@ disable_touchid() { echo "" return 0 else - rm -f "$temp_file" 2> /dev/null || true log_error "Failed to disable Touch ID" return 1 fi diff --git a/cmd/status/metrics.go b/cmd/status/metrics.go index d3acd86..9029a88 100644 --- a/cmd/status/metrics.go +++ b/cmd/status/metrics.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "os/exec" + "sync" "time" "github.com/shirou/gopsutil/v3/disk" @@ -155,34 +156,67 @@ func NewCollector() *Collector { func (c *Collector) Collect() (MetricsSnapshot, error) { now := time.Now() + + // Start host info collection early (it's fast but good to parallelize if possible, + // but it returns a struct needed for result, so we can just run it here or in parallel) + // host.Info is usually cached by gopsutil but let's just call it. hostInfo, _ := host.Info() - cpuStats, cpuErr := collectCPU() - memStats, memErr := collectMemory() - diskStats, diskErr := collectDisks() - hwInfo := collectHardware(memStats.Total, diskStats) - diskIO := c.collectDiskIO(now) - netStats, netErr := c.collectNetwork(now) - proxyStats := collectProxy() - batteryStats, _ := collectBatteries() - thermalStats := collectThermal() - sensorStats, _ := collectSensors() - gpuStats, gpuErr := c.collectGPU(now) - btStats := c.collectBluetooth(now) - topProcs := collectTopProcesses() + var ( + wg sync.WaitGroup + errMu sync.Mutex + mergeErr error - var mergeErr error - for _, e := range []error{cpuErr, memErr, diskErr, netErr, gpuErr} { - if e != nil { - if mergeErr == nil { - mergeErr = e - } else { - mergeErr = fmt.Errorf("%v; %w", mergeErr, e) + cpuStats CPUStatus + memStats MemoryStatus + diskStats []DiskStatus + diskIO DiskIOStatus + netStats []NetworkStatus + proxyStats ProxyStatus + batteryStats []BatteryStatus + thermalStats ThermalStatus + sensorStats []SensorReading + gpuStats []GPUStatus + btStats []BluetoothDevice + topProcs []ProcessInfo + ) + + // Helper to launch concurrent collection + collect := func(fn func() error) { + wg.Add(1) + go func() { + defer wg.Done() + if err := fn(); err != nil { + errMu.Lock() + if mergeErr == nil { + mergeErr = err + } else { + mergeErr = fmt.Errorf("%v; %w", mergeErr, err) + } + errMu.Unlock() } - } + }() } - // Calculate health score + // Launch all independent collection tasks + collect(func() (err error) { cpuStats, err = collectCPU(); return }) + collect(func() (err error) { memStats, err = collectMemory(); return }) + collect(func() (err error) { diskStats, err = collectDisks(); return }) + collect(func() (err error) { diskIO = c.collectDiskIO(now); return nil }) + collect(func() (err error) { netStats, err = c.collectNetwork(now); return }) + collect(func() (err error) { proxyStats = collectProxy(); return nil }) + collect(func() (err error) { batteryStats, _ = collectBatteries(); return nil }) + collect(func() (err error) { thermalStats = collectThermal(); return nil }) + collect(func() (err error) { sensorStats, _ = collectSensors(); return nil }) + collect(func() (err error) { gpuStats, err = c.collectGPU(now); return }) + collect(func() (err error) { btStats = c.collectBluetooth(now); return nil }) + collect(func() (err error) { topProcs = collectTopProcesses(); return nil }) + + // Wait for all to complete + wg.Wait() + + // Dependent tasks (must run after others) + hwInfo := collectHardware(memStats.Total, diskStats) score, scoreMsg := calculateHealthScore(cpuStats, memStats, diskStats, diskIO, thermalStats) return MetricsSnapshot{ diff --git a/tests/debug_logging.bats b/tests/debug_logging.bats new file mode 100644 index 0000000..c8768e5 --- /dev/null +++ b/tests/debug_logging.bats @@ -0,0 +1,65 @@ +#!/usr/bin/env bats + +setup_file() { + PROJECT_ROOT="$(cd "${BATS_TEST_DIRNAME}/.." && pwd)" + export PROJECT_ROOT + + ORIGINAL_HOME="${HOME:-}" + export ORIGINAL_HOME + + HOME="$(mktemp -d "${BATS_TEST_DIRNAME}/tmp-debug-logging.XXXXXX")" + export HOME + + mkdir -p "$HOME" +} + +teardown_file() { + rm -rf "$HOME" + if [[ -n "${ORIGINAL_HOME:-}" ]]; then + export HOME="$ORIGINAL_HOME" + fi +} + +setup() { + export TERM="xterm-256color" + rm -rf "${HOME:?}"/* + mkdir -p "$HOME/.config/mole" +} + +@test "mo clean --debug creates debug log file" { + run env HOME="$HOME" MO_DEBUG=1 "$PROJECT_ROOT/mole" clean --dry-run + [ "$status" -eq 0 ] + MOLE_OUTPUT="$output" + + # Check if log file exists + DEBUG_LOG="$HOME/.config/mole/mole_debug_session.log" + [ -f "$DEBUG_LOG" ] + + # Validates log content + run grep "Mole Debug Session" "$DEBUG_LOG" + [ "$status" -eq 0 ] + + # Validates standard output message (ignoring colors) + [[ "$MOLE_OUTPUT" =~ "Debug session log saved to" ]] +} + +@test "mo clean without debug does not show debug log path" { + run env HOME="$HOME" MO_DEBUG=0 "$PROJECT_ROOT/mole" clean --dry-run + [ "$status" -eq 0 ] + + [[ "$output" != *"Debug session log saved to"* ]] +} + +@test "mo clean --debug logs system info" { + run env HOME="$HOME" MO_DEBUG=1 "$PROJECT_ROOT/mole" clean --dry-run + [ "$status" -eq 0 ] + + DEBUG_LOG="$HOME/.config/mole/mole_debug_session.log" + + # Check for system info headers + run grep "User:" "$DEBUG_LOG" + [ "$status" -eq 0 ] + + run grep "Architecture:" "$DEBUG_LOG" + [ "$status" -eq 0 ] +}