Created
March 19, 2025 08:27
-
-
Save Omustardo/ae8b22ba426904acffe96b9ca74c0839 to your computer and use it in GitHub Desktop.
Test tools for a Fyne Layout bug
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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/" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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/" |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
These scripts were used for fyne-io/fyne#5615
Most of my testing was with
./layout_test_quick.sh game.go 500 --keep-openThis 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.