diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..f6e66a9 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,133 @@ +name: CI + +on: + push: + branches: [ main, master ] + tags: + - 'v*' + pull_request: + branches: [ main, master ] + workflow_dispatch: + +jobs: + test-common: + name: Run Common Tests + runs-on: [self-hosted, macOS] + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Flutter + run: | + flutter --version + flutter doctor -v + + - name: Get dependencies + run: flutter pub get + + - name: Run common tests + run: ./scripts/ci-test-common.sh + + test-linux: + name: Run Linux-Specific Tests + runs-on: [self-hosted, Linux] + needs: test-common + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Flutter + run: | + export PATH="$PATH:$HOME/flutter/bin" + flutter --version + flutter doctor -v + + - name: Get dependencies + run: | + export PATH="$PATH:$HOME/flutter/bin" + flutter pub get + + - name: Run Linux-specific tests + run: ./scripts/ci-test-linux.sh + + build-macos: + name: Build macOS + runs-on: [self-hosted, macOS] + needs: test-common + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Flutter + run: | + flutter --version + flutter doctor -v + + - name: Get dependencies + run: flutter pub get + + - name: Build macOS app + run: ./scripts/ci-build-macos.sh + + - name: Upload macOS artifact + uses: actions/upload-artifact@v4 + with: + name: aks-macos + path: | + build/macos/Build/Products/Release/aks.app + retention-days: 7 + + build-linux: + name: Build Linux (Flatpak) + runs-on: [self-hosted, Linux] + needs: [test-common, test-linux] + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Flutter + run: | + export PATH="$PATH:$HOME/flutter/bin" + flutter --version + flutter doctor -v + + - name: Get dependencies + run: | + export PATH="$PATH:$HOME/flutter/bin" + flutter pub get + + - name: Build Linux app + run: | + export PATH="$PATH:$HOME/flutter/bin" + ./scripts/ci-build-linux-native.sh + + - name: Upload Linux Flatpak + uses: actions/upload-artifact@v4 + with: + name: aks-linux-flatpak + path: aks.flatpak + retention-days: 7 + + + release: + name: Create Release + runs-on: [self-hosted, macOS] + needs: [build-macos, build-linux] + if: startsWith(github.ref, 'refs/tags/v') + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Download all artifacts + uses: actions/download-artifact@v4 + + - name: Create Release + uses: softprops/action-gh-release@v1 + with: + files: | + aks-macos/aks.app + aks-linux-flatpak/aks.flatpak + draft: false + prerelease: false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/scripts/ci-build-linux-native.sh b/scripts/ci-build-linux-native.sh new file mode 100755 index 0000000..2f3b345 --- /dev/null +++ b/scripts/ci-build-linux-native.sh @@ -0,0 +1,62 @@ +#!/bin/bash +set -e + +echo "===================================" +echo "Building aks for Linux (Native)" +echo "===================================" + +# Add Flutter to PATH if not already there +export PATH="$PATH:$HOME/flutter/bin" + +# Clean previous builds +echo "Cleaning previous builds..." +flutter clean + +# Get dependencies +echo "Getting dependencies..." +flutter pub get + +# Build Linux app +echo "Building Linux app (release mode)..." +flutter build linux --release --tree-shake-icons + +# Check if build was successful +if [ ! -f "build/linux/x64/release/bundle/aks" ]; then + echo "Error: Linux build failed" + exit 1 +fi + +echo "Linux build complete!" + +# Generate icons if needed +if [ -f "generate_app_icons.py" ]; then + echo "Generating app icons..." + python3 generate_app_icons.py || true +fi + +# Build Flatpak if manifest exists and flatpak-builder is available +if [ -f "dev.myyc.aks.yaml" ] && command -v flatpak-builder &> /dev/null; then + echo "===================================" + echo "Building Flatpak" + echo "===================================" + + # Ensure Flatpak runtime is installed + flatpak install --user -y flathub org.freedesktop.Platform//24.08 || true + flatpak install --user -y flathub org.freedesktop.Sdk//24.08 || true + + # Build Flatpak (using cache if available) + # First build creates the app + flatpak-builder --ccache --keep-build-dirs --repo=repo build-dir dev.myyc.aks.yaml + + # Build single-file bundle + flatpak build-bundle repo aks.flatpak dev.myyc.aks + + echo "Flatpak build complete!" +else + echo "Skipping Flatpak build (flatpak-builder not found or manifest missing)" +fi + + +echo "===================================" +echo "Linux builds completed successfully!" +echo "===================================" \ No newline at end of file diff --git a/scripts/ci-build-macos.sh b/scripts/ci-build-macos.sh new file mode 100755 index 0000000..d2f34c0 --- /dev/null +++ b/scripts/ci-build-macos.sh @@ -0,0 +1,63 @@ +#!/bin/bash +set -e + +echo "===================================" +echo "Building aks for macOS" +echo "===================================" + +# Flutter should be available via Homebrew in PATH + +# Clean previous builds +echo "Cleaning previous builds..." +flutter clean + +# Get dependencies +echo "Getting dependencies..." +flutter pub get + +# Run code generation if needed +if [ -f "build_runner.yaml" ]; then + echo "Running code generation..." + flutter pub run build_runner build --delete-conflicting-outputs +fi + +# Build macOS app +echo "Building macOS app..." +flutter build macos --release + +# Check if build was successful +if [ ! -d "build/macos/Build/Products/Release/aks.app" ]; then + echo "Error: macOS build failed - app bundle not found" + exit 1 +fi + +# Get version from pubspec.yaml +VERSION=$(grep "^version:" pubspec.yaml | cut -d' ' -f2 | cut -d'+' -f1) +echo "Built version: $VERSION" + +# Optional: Create a DMG for distribution +# Skip DMG creation in CI environments to avoid timeouts +if [ "$CI" = "true" ] || [ "$GITHUB_ACTIONS" = "true" ]; then + echo "Running in CI environment, skipping DMG creation" +elif command -v create-dmg &> /dev/null; then + echo "Creating DMG..." + create-dmg \ + --volname "aks" \ + --window-pos 200 120 \ + --window-size 600 400 \ + --icon-size 100 \ + --icon "aks.app" 150 150 \ + --hide-extension "aks.app" \ + --app-drop-link 450 150 \ + --no-internet-enable \ + "aks-$VERSION.dmg" \ + "build/macos/Build/Products/Release/" + echo "DMG created: aks-$VERSION.dmg" +else + echo "Note: create-dmg not found, skipping DMG creation" + echo "Install with: npm install -g create-dmg" +fi + +echo "===================================" +echo "macOS build completed successfully!" +echo "====================================" \ No newline at end of file diff --git a/scripts/ci-test-common.sh b/scripts/ci-test-common.sh new file mode 100755 index 0000000..971ceee --- /dev/null +++ b/scripts/ci-test-common.sh @@ -0,0 +1,47 @@ +#!/bin/bash +set -e + +echo "===================================" +echo "Running aks common tests" +echo "==================================" + +# Flutter should be available via Homebrew in PATH + +# Get dependencies first +echo "Getting dependencies..." +flutter pub get + +# Analyze code for issues +echo "Running Flutter analyze..." +flutter analyze --no-fatal-infos --no-fatal-warnings || { + echo "Warning: Flutter analyze found issues (continuing anyway)" +} + +# Format check (optional - can be strict) +echo "Checking code formatting..." +dart format --set-exit-if-changed --output=none . || { + echo "Warning: Code formatting issues found" + echo "Run 'dart format .' to fix formatting" + # Uncomment the next line to make formatting checks strict + # exit 1 +} + +# Run common tests (exclude platform-specific tests) +echo "Running common tests..." +if [ -d "test" ] && [ "$(ls -A test/*.dart 2>/dev/null)" ]; then + # Run tests in test/ directory but exclude test/linux/ + # Run specific test directories instead of using non-existent --exclude + flutter test --coverage --no-pub test/models/ test/widget_test.dart + + # If you want to check coverage threshold (requires lcov) + if command -v lcov &> /dev/null && [ -f "coverage/lcov.info" ]; then + echo "Generating coverage report..." + lcov --summary coverage/lcov.info + fi +else + echo "No tests found in test/ directory" +fi + +echo "===================================" +echo "Common tests completed!" +echo "====================================" \ No newline at end of file diff --git a/scripts/ci-test-linux.sh b/scripts/ci-test-linux.sh new file mode 100755 index 0000000..d2059de --- /dev/null +++ b/scripts/ci-test-linux.sh @@ -0,0 +1,36 @@ +#!/bin/bash +set -e + +echo "===================================" +echo "Running aks Linux-specific tests" +echo "==================================" + +# Add Flutter to PATH if not already there +export PATH="$PATH:$HOME/flutter/bin" + +# Get dependencies first +echo "Getting dependencies..." +flutter pub get + +# Build native Linux libraries for tests +echo "Building Linux native libraries..." +if [ -f "scripts/build_test_libs.sh" ]; then + bash scripts/build_test_libs.sh +else + echo "Warning: build_test_libs.sh not found, skipping native library build" +fi + +# Run Linux-specific tests +echo "Running Linux-specific tests..." +if [ -d "test/linux" ] && [ "$(ls -A test/linux/*.dart 2>/dev/null)" ]; then + # Run tests in test/linux/ directory + flutter test --no-pub test/linux/ + + echo "Linux-specific tests completed!" +else + echo "No Linux-specific tests found" +fi + +echo "===================================" +echo "Linux tests completed!" +echo "====================================" \ No newline at end of file diff --git a/scripts/ci-test.sh b/scripts/ci-test.sh new file mode 100755 index 0000000..10dabf9 --- /dev/null +++ b/scripts/ci-test.sh @@ -0,0 +1,59 @@ +#!/bin/bash +set -e + +echo "===================================" +echo "Running aks tests" +echo "===================================" + +# Flutter should be available via Homebrew in PATH + +# Get dependencies first +echo "Getting dependencies..." +flutter pub get + +# Analyze code for issues +echo "Running Flutter analyze..." +flutter analyze --no-fatal-infos --no-fatal-warnings || { + echo "Warning: Flutter analyze found issues (continuing anyway)" +} + +# Format check (optional - can be strict) +echo "Checking code formatting..." +dart format --set-exit-if-changed --output=none . || { + echo "Warning: Code formatting issues found" + echo "Run 'dart format .' to fix formatting" + # Uncomment the next line to make formatting checks strict + # exit 1 +} + +# Build native libraries for tests +echo "Building native libraries..." +if [ -f "scripts/build_test_libs.sh" ]; then + bash scripts/build_test_libs.sh +else + echo "Warning: build_test_libs.sh not found, skipping native library build" +fi + +# Run tests +echo "Running unit tests..." +if [ -d "test" ] && [ "$(ls -A test/*.dart 2>/dev/null)" ]; then + flutter test --coverage --no-pub + + # If you want to check coverage threshold (requires lcov) + if command -v lcov &> /dev/null && [ -f "coverage/lcov.info" ]; then + echo "Generating coverage report..." + lcov --summary coverage/lcov.info + fi +else + echo "No tests found in test/ directory" +fi + +# Run integration tests if they exist +if [ -d "integration_test" ] && [ "$(ls -A integration_test/*.dart 2>/dev/null)" ]; then + echo "Running integration tests..." + flutter test integration_test/ +fi + +echo "===================================" +echo "All tests completed!" +echo "====================================" \ No newline at end of file diff --git a/test/processors/crop_comparison_test.dart b/test/linux/crop_comparison_test.dart similarity index 95% rename from test/processors/crop_comparison_test.dart rename to test/linux/crop_comparison_test.dart index 13db13a..3fcfb03 100644 --- a/test/processors/crop_comparison_test.dart +++ b/test/linux/crop_comparison_test.dart @@ -3,6 +3,7 @@ import 'dart:typed_data'; import 'dart:math' as math; import 'dart:ui' as ui; import 'package:flutter_test/flutter_test.dart'; + import 'package:aks/services/processors/cpu_processor.dart'; import 'package:aks/services/processors/vulkan_processor.dart'; import 'package:aks/services/processors/vulkan/vulkan_bindings.dart'; @@ -190,8 +191,8 @@ void main() { print(' Pixels with diff > 1: $diffCount'); } - test('Portrait crop (2:3) on landscape image', () async { - // This is the failing case - portrait crop on landscape image + test('Portrait crop region on landscape image', () async { + // Portrait crop region (full height, center 30%) final cropRect = CropRect( left: 0.35, top: 0.0, @@ -218,9 +219,9 @@ void main() { verifyPortraitOrientation(cpuResult, 'CPU Portrait crop'); verifyPortraitOrientation(gpuResult, 'GPU Portrait crop'); - // Verify aspect ratio (2:3 = 0.667) - verifyAspectRatio(cpuResult, 2.0/3.0, 'CPU Portrait crop'); - verifyAspectRatio(gpuResult, 2.0/3.0, 'GPU Portrait crop'); + // Verify aspect ratio (should be 300x600 = 0.5) + verifyAspectRatio(cpuResult, 0.5, 'CPU Portrait crop'); + verifyAspectRatio(gpuResult, 0.5, 'GPU Portrait crop'); // Verify pixel content verifyPixelContent(cpuResult, gpuResult, 'Portrait crop content'); @@ -250,15 +251,17 @@ void main() { verifyPixelContent(cpuResult, gpuResult, 'Landscape crop content'); }); - test('Square crop (1:1) on landscape image', () async { + test('Square crop region on landscape image', () async { + // For a square crop on 1000x600, we need equal width and height + // Let's make it 400x400: width=40% (0.3-0.7), height=66.7% (0.1667-0.8333) final cropRect = CropRect( left: 0.3, - top: 0.1, + top: 0.1667, right: 0.7, - bottom: 0.9, + bottom: 0.8333, ); - print('\n=== Testing Square Crop (1:1) on Landscape Image ==='); + print('\n=== Testing Square Crop Region on Landscape Image ==='); final cpuResult = await processCropWithCPU(testPixels, imageWidth, imageHeight, cropRect); final gpuResult = await processCropWithGPU(testPixels, imageWidth, imageHeight, cropRect); diff --git a/test/processors/debug_crop_test.dart b/test/linux/debug_crop_test.dart similarity index 99% rename from test/processors/debug_crop_test.dart rename to test/linux/debug_crop_test.dart index 9b44a87..cc5fb20 100644 --- a/test/processors/debug_crop_test.dart +++ b/test/linux/debug_crop_test.dart @@ -1,5 +1,6 @@ import 'dart:typed_data'; import 'package:flutter_test/flutter_test.dart'; + import 'package:aks/services/processors/vulkan_processor.dart'; import 'package:aks/services/processors/vulkan/vulkan_bindings.dart'; import 'package:aks/models/crop_state.dart'; diff --git a/test/processors/processor_comparison_test.dart b/test/linux/processor_comparison_test.dart similarity index 99% rename from test/processors/processor_comparison_test.dart rename to test/linux/processor_comparison_test.dart index 2c7c636..208f0ff 100644 --- a/test/processors/processor_comparison_test.dart +++ b/test/linux/processor_comparison_test.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'dart:typed_data'; import 'dart:math' as math; import 'package:flutter_test/flutter_test.dart'; + import 'package:aks/services/processors/cpu_processor.dart'; import 'package:aks/services/processors/vulkan_processor.dart'; import 'package:aks/services/processors/vulkan/vulkan_bindings.dart'; diff --git a/test/test_helper.dart b/test/test_helper.dart index e1c6455..9774eb1 100644 --- a/test/test_helper.dart +++ b/test/test_helper.dart @@ -1,15 +1,36 @@ import 'dart:io'; +import 'package:flutter/foundation.dart' show TargetPlatform; /// Test helper to ensure native libraries are built before running tests class TestHelper { static bool _initialized = false; - /// Ensure test environment is set up + /// Ensure test environment is set up for the current platform static Future ensureInitialized() async { if (_initialized) return; - print('Checking test environment...'); + print('Checking test environment for ${currentPlatform}...'); + // Only build Linux libraries on Linux + if (currentPlatform == 'linux') { + await _ensureLinuxLibraries(); + } else { + print('Skipping native library build on $currentPlatform'); + } + + _initialized = true; + } + + /// Get current platform string + static String get currentPlatform { + if (Platform.isLinux) return 'linux'; + if (Platform.isMacOS) return 'macos'; + if (Platform.isWindows) return 'windows'; + return 'unknown'; + } + + /// Ensure Linux native libraries are built and available + static Future _ensureLinuxLibraries() async { // Check if libraries exist final librawPath = 'linux/libraw_processor.so'; final vulkanPath = 'linux/libvulkan_processor.so'; @@ -33,7 +54,7 @@ class TestHelper { } if (needsBuild) { - print('Building native libraries...'); + print('Building Linux native libraries...'); // Check if build script exists final buildScript = File('scripts/build_test_libs.sh'); @@ -54,12 +75,24 @@ class TestHelper { throw Exception('Failed to build native libraries'); } - print('Native libraries built successfully'); + print('Linux native libraries built successfully'); } else { - print('All native libraries found'); + print('All Linux native libraries found'); + } + } + + /// Check if a specific library is available + static bool isLibraryAvailable(String libraryName) { + switch (libraryName) { + case 'vulkan': + return currentPlatform == 'linux' && + File('linux/libvulkan_processor.so').existsSync(); + case 'raw': + return currentPlatform == 'linux' && + File('linux/libraw_processor.so').existsSync(); + default: + return false; } - - _initialized = true; } /// Clean up test artifacts if needed diff --git a/test/widget_test.dart b/test/widget_test.dart index 15525db..27ad270 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -11,20 +11,11 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:aks/main.dart'; void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { + testWidgets('App loads correctly', (WidgetTester tester) async { // Build our app and trigger a frame. - await tester.pumpWidget(const MyApp()); + await tester.pumpWidget(const AksApp()); - // Verify that our counter starts at 0. - expect(find.text('0'), findsOneWidget); - expect(find.text('1'), findsNothing); - - // Tap the '+' icon and trigger a frame. - await tester.tap(find.byIcon(Icons.add)); - await tester.pump(); - - // Verify that our counter has incremented. - expect(find.text('0'), findsNothing); - expect(find.text('1'), findsOneWidget); + // Verify that the app loads without crashing + expect(find.byType(MaterialApp), findsOneWidget); }); }