Skip to content

Instantly share code, notes, and snippets.

@Omustardo
Created March 19, 2025 08:27
Show Gist options
  • Select an option

  • Save Omustardo/ae8b22ba426904acffe96b9ca74c0839 to your computer and use it in GitHub Desktop.

Select an option

Save Omustardo/ae8b22ba426904acffe96b9ca74c0839 to your computer and use it in GitHub Desktop.
Test tools for a Fyne Layout bug
#!/bin/bash
# Flexible Layout Test Script for Fyne applications
# Usage: ./layout_test.sh [path] [iterations]
# Examples:
# ./layout_test.sh foo/main.go 10 # Specific Go file with 10 iterations
# Parse arguments
TARGET_PATH="${1:-.}" # Default to current directory if not specified
ITERATIONS="${2:-20}" # Default to 20 iterations if not specified
SLEEP_BEFORE_SCREENSHOT=0.5
SLEEP_AFTER_KILL=0.1
# Convert TARGET_PATH to absolute path if it isn't already
if [[ "$TARGET_PATH" != /* ]]; then
TARGET_PATH="$(pwd)/$TARGET_PATH"
fi
# Check if parent directory is set (from layout_test_foreach.sh)
PARENT_DIR="${LAYOUT_RESULTS_PARENT_DIR:-}"
# Convert PARENT_DIR to absolute path if it isn't already
if [ -n "$PARENT_DIR" ] && [[ "$PARENT_DIR" != /* ]]; then
PARENT_DIR="$(pwd)/$PARENT_DIR"
fi
# Determine if target is a directory or file
if [ -d "$TARGET_PATH" ]; then
echo "Error: Cannot test a directory directly. Please use layout_test_foreach.sh instead."
exit 1
fi
# Ensure target is a Go file
if [ ! -f "$TARGET_PATH" ] || [[ "$TARGET_PATH" != *.go ]]; then
echo "Error: Target must be a Go file."
echo "Usage: $0 path/to/file.go [iterations]"
exit 1
fi
# Get filenames and paths
TARGET_DIR=$(dirname "$TARGET_PATH")
TARGET_FILE=$(basename "$TARGET_PATH")
TARGET_BASE="${TARGET_FILE%.go}"
UNIQUE_ID="${TARGET_BASE}_$(date +%s)" # Add timestamp for uniqueness
# Create temporary directories with unique names
TEMP_DIR="/tmp/fyne_layout_test_${UNIQUE_ID}"
SCREENSHOTS_DIR="${TEMP_DIR}/screenshots"
# If a parent directory is specified, place results there
if [ -n "$PARENT_DIR" ]; then
# Use a different approach - don't nest directories too deeply
RESULTS_DIR="$PARENT_DIR/results_${TARGET_BASE}"
else
RESULTS_DIR="$(pwd)/layout_results_${UNIQUE_ID}"
fi
# Make sure all directories exist
mkdir -p "$TEMP_DIR"
mkdir -p "$SCREENSHOTS_DIR"
mkdir -p "$RESULTS_DIR"
# Verify directories were created
for dir in "$TEMP_DIR" "$SCREENSHOTS_DIR" "$RESULTS_DIR"; do
if [ ! -d "$dir" ]; then
echo "ERROR: Failed to create directory: $dir"
exit 1
fi
done
echo "=== Fyne Layout Test ==="
echo "Target: $TARGET_PATH"
echo "Iterations: $ITERATIONS"
echo "Results will be saved to: $RESULTS_DIR"
# Build the application
echo "Building application..."
go build -o "${TEMP_DIR}/app_binary" "$TARGET_PATH"
if [ $? -ne 0 ]; then
echo "Build failed!"
rm -rf "$TEMP_DIR"
exit 1
fi
# Function to get the window ID of our application
get_window_id() {
window_id=$(xdotool search --name "Fyne Layout Test" | head -n 1)
echo "$window_id"
}
# Function to take a screenshot of the application window
take_screenshot() {
local iteration=$1
local window_id=$2
local output_file="$SCREENSHOTS_DIR/screenshot_$iteration.png"
# Take screenshot of specific window
gnome-screenshot -w -f "$output_file"
if [ -f "$output_file" ]; then
echo "Screenshot saved to $output_file"
echo "$output_file"
return 0
else
echo "Error: Failed to save screenshot to $output_file"
return 1
fi
}
# Counter for successful screenshots
SUCCESSFUL_SCREENSHOTS=0
# Run the test iterations
echo "Starting $ITERATIONS test iterations..."
for i in $(seq 1 $ITERATIONS); do
# echo "Iteration $i/$ITERATIONS"
# Start the application
"${TEMP_DIR}/app_binary" &
APP_PID=$!
# Wait for the application to initialize
sleep $SLEEP_BEFORE_SCREENSHOT
# Get window ID
WINDOW_ID=$(get_window_id)
if [ -z "$WINDOW_ID" ]; then
echo "Could not find application window, skipping iteration"
kill $APP_PID 2>/dev/null || true
sleep $SLEEP_AFTER_KILL
continue
fi
# Activate window and take screenshot
xdotool windowactivate "$WINDOW_ID"
sleep 0.5 # Give it time to activate
SCREENSHOT=$(take_screenshot $i "$WINDOW_ID")
# Increment counter if screenshot was taken
if [ -f "$SCREENSHOTS_DIR/screenshot_$i.png" ]; then
SUCCESSFUL_SCREENSHOTS=$((SUCCESSFUL_SCREENSHOTS + 1))
fi
# Kill the application
kill $APP_PID 2>/dev/null || true
sleep $SLEEP_AFTER_KILL
done
echo "All iterations completed. Analyzing screenshots..."
# Create a placeholder file in case we don't get any results
echo "Creating placeholder in case needed"
echo "P3 100 100 255 255 255 255 255 255 255" > "$TEMP_DIR/placeholder.ppm"
# Check if any screenshots were captured
if [ $SUCCESSFUL_SCREENSHOTS -eq 0 ]; then
echo "No screenshots were captured successfully. Creating placeholder result."
echo "This could be due to window detection issues or application startup problems."
# Copy the placeholder to the results dir
cp "$TEMP_DIR/placeholder.ppm" "$RESULTS_DIR/img1_count0.png"
echo "Analysis skipped due to no screenshots."
echo "Cleaning up temporary files..."
rm -rf "$TEMP_DIR"
echo "Done. Results (placeholder) are in $RESULTS_DIR/"
exit 0
fi
# Analyze the screenshots to find unique layouts
cd "$SCREENSHOTS_DIR" || exit 1
# Check for any PNG files before trying to hash them
SCREENSHOT_COUNT=$(ls -1 *.png 2>/dev/null | wc -l)
if [ "$SCREENSHOT_COUNT" -gt 0 ]; then
# Create a directory for each unique image and count occurrences
declare -A image_counts
declare -A image_hashes
# Generate hash for each image
for img in *.png; do
# Use md5sum to generate a hash
hash=$(md5sum "$img" | awk '{print $1}')
if [[ -z "${image_hashes[$hash]}" ]]; then
# First time seeing this hash
image_hashes[$hash]="$img"
image_counts[$hash]=1
else
# We've seen this hash before
image_counts[$hash]=$((image_counts[$hash] + 1))
fi
done
# Now check if we found any hashes
if [ ${#image_hashes[@]} -eq 0 ]; then
echo "No valid image hashes found. Creating placeholder."
cp "$TEMP_DIR/placeholder.ppm" "$RESULTS_DIR/img1_count0.png"
else
# Copy unique images with count in filename
echo "Creating result files directly in $RESULTS_DIR"
count=1
for hash in "${!image_hashes[@]}"; do
orig_file="${image_hashes[$hash]}"
count_val="${image_counts[$hash]}"
src_file="$SCREENSHOTS_DIR/$orig_file"
dest_file="$RESULTS_DIR/img${count}_count${count_val}.png"
echo "Copying: $src_file -> $dest_file"
if [ -f "$src_file" ]; then
# Make sure results directory exists before copying
mkdir -p "$RESULTS_DIR"
cp "$src_file" "$dest_file"
if [ $? -ne 0 ]; then
echo "ERROR: Copy failed for file: $src_file"
# Create placeholder since copy failed
cp "$TEMP_DIR/placeholder.ppm" "$dest_file"
fi
# Calculate and display correct percentage
percent=$(( (count_val * 100) / SUCCESSFUL_SCREENSHOTS ))
echo "Image $count appeared $count_val times (${percent}%)"
count=$((count + 1))
else
echo "WARNING: Source file not found: $src_file"
fi
done
fi
# Calculate percentage distribution
total_unique=$(( count - 1 ))
echo "Analysis complete. Results saved to $RESULTS_DIR directory."
echo "Found $total_unique unique layouts in $SUCCESSFUL_SCREENSHOTS successful runs (out of $ITERATIONS attempts)."
else
echo "No screenshot PNG files found for analysis."
echo "Creating placeholder result."
mkdir -p "$RESULTS_DIR" # Ensure directory exists
cp "$TEMP_DIR/placeholder.ppm" "$RESULTS_DIR/img1_count0.png"
fi
# Cleanup temporary files
echo "Cleaning up temporary files..."
rm -rf "$TEMP_DIR"
echo "Done. Results are in $RESULTS_DIR/"
#!/bin/bash
# Flexible Layout Test Script for Fyne applications
# Usage: ./layout_test.sh [path] [iterations] [--keep-open|-k]
# Examples:
# ./layout_test.sh foo/main.go 10 # Specific Go file with 10 iterations
# ./layout_test.sh foo/main.go 10 --keep-open # Keep app open if diff found
# ./layout_test.sh foo/main.go 10 -k # Short form of keep-open flag
# Modified to exit as soon as a difference is found with sound notification
# Parse arguments
TARGET_PATH="${1:-.}" # Default to current directory if not specified
ITERATIONS="${2:-20}" # Default to 20 iterations if not specified
KEEP_OPEN=0 # Default to not keeping the program open
SLEEP_BEFORE_SCREENSHOT=0.5
SLEEP_AFTER_KILL=0.1
# Check for keep-open flag (can be in any position after the first two args)
for arg in "${@:3}"; do
if [ "$arg" = "--keep-open" ] || [ "$arg" = "-k" ]; then
KEEP_OPEN=1
echo "Keep-open flag detected: Will keep the application running if a difference is found"
fi
done
# Convert TARGET_PATH to absolute path if it isn't already
if [[ "$TARGET_PATH" != /* ]]; then
TARGET_PATH="$(pwd)/$TARGET_PATH"
fi
# Check if parent directory is set (from layout_test_foreach.sh)
PARENT_DIR="${LAYOUT_RESULTS_PARENT_DIR:-}"
# Convert PARENT_DIR to absolute path if it isn't already
if [ -n "$PARENT_DIR" ] && [[ "$PARENT_DIR" != /* ]]; then
PARENT_DIR="$(pwd)/$PARENT_DIR"
fi
# If iterations were passed from parent script, use that value
if [ -n "${LAYOUT_TEST_ITERATIONS}" ]; then
ITERATIONS="${LAYOUT_TEST_ITERATIONS}"
fi
# Determine if target is a directory or file
if [ -d "$TARGET_PATH" ]; then
echo "Error: Cannot test a directory directly. Please use layout_test_foreach.sh instead."
exit 1
fi
# Ensure target is a Go file
if [ ! -f "$TARGET_PATH" ] || [[ "$TARGET_PATH" != *.go ]]; then
echo "Error: Target must be a Go file."
echo "Usage: $0 path/to/file.go [iterations]"
exit 1
fi
# Function to play a notification sound
play_notification() {
if command -v paplay &> /dev/null; then
paplay /usr/share/sounds/freedesktop/stereo/dialog-information.oga &> /dev/null || \
paplay /usr/share/sounds/gnome/default/alerts/glass.ogg &> /dev/null || \
paplay /usr/share/sounds/ubuntu/stereo/message.ogg &> /dev/null || \
paplay /usr/share/sounds/purple/receive.wav &> /dev/null || \
paplay /usr/share/sounds/freedesktop/stereo/bell.oga &> /dev/null
fi
}
# Get filenames and paths
TARGET_DIR=$(dirname "$TARGET_PATH")
TARGET_FILE=$(basename "$TARGET_PATH")
TARGET_BASE="${TARGET_FILE%.go}"
UNIQUE_ID="${TARGET_BASE}_$(date +%s)" # Add timestamp for uniqueness
# Create temporary directories with unique names
TEMP_DIR="/tmp/fyne_layout_test_${UNIQUE_ID}"
SCREENSHOTS_DIR="${TEMP_DIR}/screenshots"
# If a parent directory is specified, place results there
if [ -n "$PARENT_DIR" ]; then
# Use a different approach - don't nest directories too deeply
RESULTS_DIR="$PARENT_DIR/results_${TARGET_BASE}"
else
RESULTS_DIR="$(pwd)/layout_results_${UNIQUE_ID}"
fi
# Make sure all directories exist
mkdir -p "$TEMP_DIR"
mkdir -p "$SCREENSHOTS_DIR"
mkdir -p "$RESULTS_DIR"
# Verify directories were created
for dir in "$TEMP_DIR" "$SCREENSHOTS_DIR" "$RESULTS_DIR"; do
if [ ! -d "$dir" ]; then
echo "ERROR: Failed to create directory: $dir"
exit 1
fi
done
echo "=== Fyne Layout Test ==="
echo "Target: $TARGET_PATH"
echo "Iterations: $ITERATIONS (will stop at first difference)"
echo "Keep open on difference: $([ $KEEP_OPEN -eq 1 ] && echo "Yes" || echo "No")"
echo "Results will be saved to: $RESULTS_DIR"
# Build the application
echo "Building application..."
go build -o "${TEMP_DIR}/app_binary" "$TARGET_PATH"
if [ $? -ne 0 ]; then
echo "Build failed!"
rm -rf "$TEMP_DIR"
exit 1
fi
# Function to get the window ID of our application
get_window_id() {
window_id=$(xdotool search --name "Fyne Layout Test" | head -n 1)
echo "$window_id"
}
# Function to take a screenshot of the application window
take_screenshot() {
local iteration=$1
local window_id=$2
local output_file="$SCREENSHOTS_DIR/screenshot_$iteration.png"
# Take screenshot of specific window (quietly)
gnome-screenshot -w -f "$output_file" &> /dev/null
if [ -f "$output_file" ]; then
# Success but don't output anything
return 0
else
# Only log errors
echo "Error: Failed to save screenshot for iteration $iteration"
return 1
fi
}
# Counter for successful screenshots
SUCCESSFUL_SCREENSHOTS=0
REFERENCE_HASH=""
DIFFERENCE_FOUND=0
# Run the test iterations (with reduced logging)
echo "Starting test iterations (max $ITERATIONS, only logging differences)..."
for i in $(seq 1 $ITERATIONS); do
# Start the application (quietly - only show iteration number if verbose)
# echo "Iteration $i/$ITERATIONS"
"${TEMP_DIR}/app_binary" &
APP_PID=$!
# Wait for the application to initialize
sleep $SLEEP_BEFORE_SCREENSHOT
# Get window ID
WINDOW_ID=$(get_window_id)
if [ -z "$WINDOW_ID" ]; then
# Skip silently
kill $APP_PID 2>/dev/null || true
sleep $SLEEP_AFTER_KILL
continue
fi
# Activate window and take screenshot
xdotool windowactivate "$WINDOW_ID"
sleep 0.5 # Give it time to activate
SCREENSHOT=$(take_screenshot $i "$WINDOW_ID" > /dev/null 2>&1)
# Check if screenshot was successful
if [ -f "$SCREENSHOTS_DIR/screenshot_$i.png" ]; then
SUCCESSFUL_SCREENSHOTS=$((SUCCESSFUL_SCREENSHOTS + 1))
# Get hash of current screenshot
CURRENT_HASH=$(md5sum "$SCREENSHOTS_DIR/screenshot_$i.png" | awk '{print $1}')
# If this is the first successful screenshot, save its hash as reference
if [ $SUCCESSFUL_SCREENSHOTS -eq 1 ]; then
REFERENCE_HASH=$CURRENT_HASH
echo "Reference image captured. Testing iterations..."
else
# Compare with reference hash
if [ "$CURRENT_HASH" != "$REFERENCE_HASH" ]; then
echo "DIFFERENCE DETECTED at iteration $i!"
DIFFERENCE_FOUND=1
# Play notification sound
play_notification
# Save the reference and different screenshots to results dir
cp "$SCREENSHOTS_DIR/screenshot_1.png" "$RESULTS_DIR/img1_reference.png"
cp "$SCREENSHOTS_DIR/screenshot_$i.png" "$RESULTS_DIR/img2_different.png"
# Check if we should keep the application running
if [ $KEEP_OPEN -eq 1 ]; then
echo "Application left running for inspection (PID: $APP_PID)"
echo "Press Enter to continue and close the application..."
read -r
kill $APP_PID 2>/dev/null || true
sleep $SLEEP_AFTER_KILL
else
# Kill the application
kill $APP_PID 2>/dev/null || true
sleep $SLEEP_AFTER_KILL
fi
break # Exit the loop as we found a difference
fi
fi
fi
# Kill the application
kill $APP_PID 2>/dev/null || true
sleep $SLEEP_AFTER_KILL
done
# Create a placeholder file in case we don't get any results
echo "Creating placeholder in case needed"
echo "P3 100 100 255 255 255 255 255 255 255" > "$TEMP_DIR/placeholder.ppm"
# Check if any screenshots were captured
if [ $SUCCESSFUL_SCREENSHOTS -eq 0 ]; then
echo "No screenshots were captured successfully. Creating placeholder result."
echo "This could be due to window detection issues or application startup problems."
# Copy the placeholder to the results dir
cp "$TEMP_DIR/placeholder.ppm" "$RESULTS_DIR/img1_count0.png"
echo "Analysis skipped due to no screenshots."
echo "Cleaning up temporary files..."
rm -rf "$TEMP_DIR"
echo "Done. Results (placeholder) are in $RESULTS_DIR/"
exit 0
fi
if [ $DIFFERENCE_FOUND -eq 0 ]; then
# No differences found, but we have screenshots
echo "No layout differences detected after $SUCCESSFUL_SCREENSHOTS iterations."
# Save one example
if [ -f "$SCREENSHOTS_DIR/screenshot_1.png" ]; then
cp "$SCREENSHOTS_DIR/screenshot_1.png" "$RESULTS_DIR/img1_stable_layout.png"
echo "Example layout saved to $RESULTS_DIR/img1_stable_layout.png"
fi
else
echo "Layout difference found. Images saved to $RESULTS_DIR/"
echo "- img1_reference.png: First captured layout"
echo "- img2_different.png: Different layout detected"
fi
# Cleanup temporary files
echo "Cleaning up temporary files..."
rm -rf "$TEMP_DIR"
echo "Done. Results are in $RESULTS_DIR/"
@Omustardo
Copy link
Author

Omustardo commented Mar 19, 2025

These scripts were used for fyne-io/fyne#5615

Most of my testing was with ./layout_test_quick.sh game.go 500 --keep-open

This compiles the binary, takes a screenshot as a reference image, and then keeps restarting and screenshotting until either 500 iterations are done or a different image is found. It then leaves the program running so that you can look at the program in its problem state.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment