Skip to content

Commit f0257a2

Browse files
authored
test(e2e): add verbose logging verification for CLI commands (#323)
Adds comprehensive E2E test coverage verifying that emulator verbose logging correctly captures Modbus operations during CLI read/write commands. ## E2E Test Coverage (4 new tests) - Read single data point with verbose logging - Read multiple data points with verbose logging - Write operation with verbose logging - Write with verification (logs both WRITE and READ) Each test verifies: - Correct operation type ([VERBOSE] READ or [VERBOSE] WRITE) - Slave ID, function code, register address, count - Specific hex values for data being read/written ## Documentation Improvements - Inline comments explain hex value conversions (e.g., 2300 = 0x08FC = 230.0V) - Register address explanations (e.g., 0x002B = device_address config parameter) - Driver behavior notes (e.g., "EX9EM reads all 11 registers in one operation") - Comments explain why specific function codes are used (0x03 vs 0x10) ## Test Infrastructure Enhanced helpers.bash with: - Verbose flag support for emulator startup - Improved log file handling (temp log + symlink approach) - New assert_emulator_log_contains helper for log verification - Comprehensive cleanup of all temporary artifacts ## Technical Details Tests verify VerboseLogger output format: [VERBOSE] {READ|WRITE} slave={id} func=0x{code} addr=0x{addr} count={n} values=[0x{val1}, ...] Known limitation: Discover command verbose logging test omitted due to emulator restart issue (tracked for follow-up). Builds on PR #322.
1 parent ae931a0 commit f0257a2

2 files changed

Lines changed: 228 additions & 9 deletions

File tree

tests/e2e/tests/04-cli.bats

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,3 +411,104 @@ teardown() {
411411
assert_failure
412412
assert_output_contains "port"
413413
}
414+
415+
@test "read command logs verbose READ operation to emulator" {
416+
# Start emulator with verbose logging enabled
417+
run start_test_emulator "fixtures/emulators/port1-single-device.json" "--verbose"
418+
assert_success
419+
EMULATOR_PID="$output"
420+
421+
# Read voltage data point from EX9EM device
422+
run node "$CLI_BIN" read \
423+
--port /tmp/ttyV1 \
424+
--slave-id 1 \
425+
--driver @ya-modbus/driver-ex9em \
426+
--data-point voltage
427+
428+
assert_success
429+
430+
# Verify emulator logged the READ operation
431+
# EX9EM driver reads all 11 data registers in one operation starting at addr 0x0000
432+
assert_emulator_log_contains "$EMULATOR_PID" "\[VERBOSE\] READ"
433+
assert_emulator_log_contains "$EMULATOR_PID" "slave=1"
434+
assert_emulator_log_contains "$EMULATOR_PID" "func=0x03"
435+
assert_emulator_log_contains "$EMULATOR_PID" "addr=0x0000"
436+
assert_emulator_log_contains "$EMULATOR_PID" "count=11"
437+
# Verify voltage value is present (2300 decimal = 0x08FC hex = 230.0V when scaled /10)
438+
assert_emulator_log_contains "$EMULATOR_PID" "0x08FC"
439+
}
440+
441+
@test "read multiple data points logs READ with multiple values" {
442+
run start_test_emulator "fixtures/emulators/port1-single-device.json" "--verbose"
443+
assert_success
444+
EMULATOR_PID="$output"
445+
446+
# Read voltage and current from EX9EM device
447+
run node "$CLI_BIN" read \
448+
--port /tmp/ttyV1 \
449+
--slave-id 1 \
450+
--driver @ya-modbus/driver-ex9em \
451+
--data-point voltage current
452+
453+
assert_success
454+
455+
# Verify READ operation logged with both values
456+
assert_emulator_log_contains "$EMULATOR_PID" "\[VERBOSE\] READ"
457+
assert_emulator_log_contains "$EMULATOR_PID" "addr=0x0000"
458+
# Verify voltage (register 0: 2300 = 0x08FC = 230.0V) and current (register 1: 520 = 0x0208 = 52.0A)
459+
assert_emulator_log_contains "$EMULATOR_PID" "0x08FC"
460+
assert_emulator_log_contains "$EMULATOR_PID" "0x0208"
461+
}
462+
463+
@test "write command logs verbose WRITE operation to emulator" {
464+
run start_test_emulator "fixtures/emulators/port1-single-device.json" "--verbose"
465+
assert_success
466+
EMULATOR_PID="$output"
467+
468+
# Write device_address configuration parameter (register 43 = 0x002B)
469+
run node "$CLI_BIN" write \
470+
--port /tmp/ttyV1 \
471+
--slave-id 1 \
472+
--driver @ya-modbus/driver-ex9em \
473+
--data-point device_address \
474+
--value 5 \
475+
--yes
476+
477+
assert_success
478+
479+
# Verify emulator logged the WRITE operation
480+
# Transport layer uses func 0x10 (Write Multiple Registers) even for single register writes
481+
assert_emulator_log_contains "$EMULATOR_PID" "\[VERBOSE\] WRITE"
482+
assert_emulator_log_contains "$EMULATOR_PID" "slave=1"
483+
assert_emulator_log_contains "$EMULATOR_PID" "func=0x10"
484+
assert_emulator_log_contains "$EMULATOR_PID" "addr=0x002B"
485+
assert_emulator_log_contains "$EMULATOR_PID" "count=1"
486+
# Verify value written (5 decimal = 0x0005 hex)
487+
assert_emulator_log_contains "$EMULATOR_PID" "0x0005"
488+
}
489+
490+
@test "write with verify logs both WRITE and READ operations" {
491+
run start_test_emulator "fixtures/emulators/port1-single-device.json" "--verbose"
492+
assert_success
493+
EMULATOR_PID="$output"
494+
495+
# Write with verification enabled
496+
run node "$CLI_BIN" write \
497+
--port /tmp/ttyV1 \
498+
--slave-id 1 \
499+
--driver @ya-modbus/driver-ex9em \
500+
--data-point device_address \
501+
--value 10 \
502+
--yes \
503+
--verify
504+
505+
assert_success
506+
507+
# Verify WRITE operation logged (10 decimal = 0x000A hex)
508+
assert_emulator_log_contains "$EMULATOR_PID" "\[VERBOSE\] WRITE"
509+
assert_emulator_log_contains "$EMULATOR_PID" "addr=0x002B"
510+
assert_emulator_log_contains "$EMULATOR_PID" "values=\[0x000A\]"
511+
512+
# Verify READ operation also logged (--verify flag triggers read-back)
513+
assert_emulator_log_contains "$EMULATOR_PID" "\[VERBOSE\] READ"
514+
}

tests/e2e/tests/helpers.bash

Lines changed: 127 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -84,19 +84,73 @@ stop_mqtt_subscriber() {
8484
}
8585

8686
# Start emulator in background using CLI
87+
#
88+
# Starts the ya-modbus emulator in the background with output redirected to a log file.
89+
# The log file is created with a unique temporary name first, then symlinked to a
90+
# PID-based name for easy access by tests.
91+
#
92+
# LOG FILE APPROACH:
93+
# Tests need to read logs via PID-based filename (/tmp/emulator-$pid.log), but we
94+
# don't know the PID until AFTER starting the background process. Shell redirection
95+
# must be specified BEFORE the command runs, creating a chicken-and-egg problem.
96+
#
97+
# SOLUTION:
98+
# 1. Create temp log with guaranteed unique name using mktemp
99+
# 2. Start emulator with output redirected to temp log
100+
# 3. Capture PID from background process
101+
# 4. Create symlink from /tmp/emulator-$pid.log -> temp log
102+
# 5. Store temp log path for cleanup
103+
#
104+
# This ensures the log file is immediately writable and later accessible via PID.
105+
#
106+
# Arguments:
107+
# $1 - config_file: Path to emulator configuration file
108+
# $2 - verbose_flag: Optional "--verbose" flag to enable verbose logging
109+
#
110+
# Returns:
111+
# Prints PID of started emulator to stdout
112+
# Returns 0 on success, 1 on failure
113+
#
114+
# Example:
115+
# run start_test_emulator "config.json"
116+
# run start_test_emulator "config.json" "--verbose"
117+
#
87118
start_test_emulator() {
88119
local config_file=$1
89-
local log_file="/tmp/emulator-$$.log"
120+
local verbose_flag=${2:-""}
90121
local project_root
91122
project_root=$(get_project_root)
92123

93-
# Use the built emulator CLI (without --quiet to enable log checking)
94-
node "$project_root/packages/emulator/dist/esm/bin/ya-modbus-emulator.js" \
95-
--config "$config_file" \
96-
> "$log_file" 2>&1 &
124+
# Create guaranteed-unique temp log file
125+
local temp_log
126+
temp_log=$(mktemp /tmp/emulator-temp-XXXXXX.log) || {
127+
echo "Failed to create temp log file" >&2
128+
return 1
129+
}
130+
131+
# Start emulator with output redirected to temp log
132+
# Use proper quoting for optional verbose flag
133+
if [ -n "$verbose_flag" ]; then
134+
node "$project_root/packages/emulator/dist/esm/bin/ya-modbus-emulator.js" \
135+
--config "$config_file" \
136+
"$verbose_flag" \
137+
> "$temp_log" 2>&1 &
138+
else
139+
node "$project_root/packages/emulator/dist/esm/bin/ya-modbus-emulator.js" \
140+
--config "$config_file" \
141+
> "$temp_log" 2>&1 &
142+
fi
97143
local pid=$!
98144

145+
# Create symlink for PID-based access
146+
local log_file="/tmp/emulator-$pid.log"
147+
if ! ln -sf "$temp_log" "$log_file"; then
148+
echo "Warning: Failed to create log symlink" >&2
149+
fi
150+
151+
# Store metadata for cleanup
99152
echo "$pid" > "/tmp/emulator-$pid.pid"
153+
echo "$temp_log" > "/tmp/emulator-$pid-logfile.txt"
100154

101155
# Wait for emulator to be ready by checking log file
102156
local timeout=50
@@ -107,12 +161,12 @@ start_test_emulator() {
107161
# Check if process is still alive
108162
if ! kill -0 "$pid" 2>/dev/null; then
109163
echo "Emulator process died during startup" >&2
110-
cat "$log_file" >&2
164+
cat "$temp_log" >&2
111165
return 1
112166
fi
113167

114168
# Check log file for "Emulator started successfully" message
115-
if [ -f "$log_file" ] && grep -q "Emulator started successfully" "$log_file" 2>/dev/null; then
169+
if [ -f "$temp_log" ] && grep -q "Emulator started successfully" "$temp_log" 2>/dev/null; then
116170
emulator_ready=1
117171
break
118172
fi
@@ -122,15 +176,14 @@ start_test_emulator() {
122176
done
123177

124178
# If readiness not detected but process alive, it may have started without logging
125-
# Skip the fallback sleep since we now have proper log output
126179
if [ $emulator_ready -eq 0 ] && kill -0 "$pid" 2>/dev/null; then
127180
echo "Warning: Emulator running but startup message not found in logs" >&2
128181
fi
129182

130183
# Final check if still running
131184
if ! kill -0 "$pid" 2>/dev/null; then
132185
echo "Failed to start emulator" >&2
133-
cat "$log_file" >&2
186+
cat "$temp_log" >&2
134187
return 1
135188
fi
136189

@@ -139,9 +192,17 @@ start_test_emulator() {
139192
}
140193

141194
# Stop emulator
195+
#
196+
# Stops the emulator process and cleans up all associated files (PID file,
197+
# log symlink, and temporary log file).
198+
#
199+
# Arguments:
200+
# $1 - pid: Process ID of the emulator to stop
201+
#
142202
stop_test_emulator() {
143203
local pid=$1
144204
local pid_file="/tmp/emulator-$pid.pid"
205+
local log_file_ref="/tmp/emulator-$pid-logfile.txt"
145206

146207
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
147208
kill "$pid" 2>/dev/null || true
@@ -181,7 +242,19 @@ stop_test_emulator() {
181242
wait "$pid" 2>/dev/null || true
182243
fi
183244

245+
# Clean up temp log file (read path from reference file)
246+
if [ -f "$log_file_ref" ]; then
247+
local temp_log
248+
temp_log=$(cat "$log_file_ref" 2>/dev/null)
249+
if [ -n "$temp_log" ]; then
250+
rm -f "$temp_log" 2>/dev/null || true
251+
fi
252+
rm -f "$log_file_ref"
253+
fi
254+
255+
# Clean up PID file and symlink
184256
rm -f "$pid_file"
257+
rm -f "/tmp/emulator-$pid.log"
185258
}
186259

187260
# Check if Docker service is healthy
@@ -238,6 +311,48 @@ assert_file_contains() {
238311
fi
239312
}
240313

314+
# Assert emulator log contains expected pattern
315+
#
316+
# Reads the emulator log file for the given PID and verifies it contains
317+
# the specified pattern using extended regex matching (grep -E).
318+
#
319+
# The log file is accessed via the symlink at /tmp/emulator-$pid.log which
320+
# points to the actual temp log file created by start_test_emulator.
321+
#
322+
# Arguments:
323+
# $1 - pid: Process ID of the emulator
324+
# $2 - expected: Pattern to search for (supports grep extended regex)
325+
#
326+
# Returns:
327+
# 0 if pattern found
328+
# 1 if log file not found or pattern not found (dumps full log to stderr)
329+
#
330+
# Examples:
331+
# assert_emulator_log_contains "$EMULATOR_PID" "\[VERBOSE\] READ"
332+
# assert_emulator_log_contains "$EMULATOR_PID" "func=0x03"
333+
# assert_emulator_log_contains "$EMULATOR_PID" "slave=1"
334+
#
335+
# Note: Brackets and special regex characters must be escaped with backslash
336+
# when using grep -E. For example, to match literal "[VERBOSE]" use "\[VERBOSE\]"
337+
#
338+
assert_emulator_log_contains() {
339+
local pid=$1
340+
local expected=$2
341+
local log_file="/tmp/emulator-$pid.log"
342+
343+
if [ ! -f "$log_file" ]; then
344+
echo "Emulator log file not found: $log_file" >&2
345+
return 1
346+
fi
347+
348+
if ! grep -qE "$expected" "$log_file"; then
349+
echo "Expected emulator log to contain: $expected" >&2
350+
echo "Emulator log contents:" >&2
351+
cat "$log_file" >&2
352+
return 1
353+
fi
354+
}
355+
241356
# Get project root directory
242357
get_project_root() {
243358
local script_dir
@@ -249,5 +364,8 @@ get_project_root() {
249364
clean_test_artifacts() {
250365
rm -f /tmp/mqtt-messages-*.txt 2>/dev/null || true
251366
rm -f /tmp/emulator-*.log 2>/dev/null || true
367+
rm -f /tmp/emulator-*.pid 2>/dev/null || true
368+
rm -f /tmp/emulator-*-logfile.txt 2>/dev/null || true
369+
rm -f /tmp/emulator-temp-*.log 2>/dev/null || true
252370
rm -f /tmp/bridge-*.log 2>/dev/null || true
253371
}

0 commit comments

Comments
 (0)