Commit a40f3bf

derdilla <82763757+derdilla@users.noreply.github.com>
2024-11-08 10:26:50
Do CI builds across flutter branches (#475)
* Use matrix to build with stable and beta flutter in parallel * Fix faulty sparse checkout * dael with tool environment visibility * add missing lang name * share src dir * unique key * use matrix for apk build * fix dir * upload resulting apks * Add linux builds * Fix indentation * remove unsupported flavor flag * fix golden change PRing * reduce test cases run on a regular basis adding channel coverage * fix mock code generation * fix matrix name mismatch * fix permissions * remove continue * generate branch specific golden images
1 parent efe1ad6
.github/workflows/app-CI.yml
@@ -7,185 +7,81 @@ on:
     paths:
       - "app/**"
       - "health_data_store/**"
-      - ".github/workflows/app-CI.yml"
+      - "extendend-testing.yml"
   workflow_dispatch:
 
-env:
-  FLUTTER_CHANNEL: 'beta'
-  DART_SDK: 'beta'
-  JAVA_VERSION: '17'
-  EMULATOR_VERSION: 'system-images;android-34;aosp_atd;x86_64'
-
 jobs:
-  unit-test:
-    name: "🧩🧪 Run unit tests"
+  run-tests:
     runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        channel:
+          - beta
+          - stable
     permissions:
       contents: write
       pull-requests: write
     steps:
-    - name: Checkout code
-      uses: actions/checkout@v4
-      with:
-        # ensures there are no unexpected directories needed
-        sparse-checkout: |
-          app
-          health_data_store
-    - name: Cache generated health data store
-      id: cache-generated
-      uses: actions/cache@v4
-      with:
-        path: health_data_store/lib
-        key: builder-${{ hashFiles('health_data_store/pubspec.yaml', 'health_data_store/lib/*', 'health_data_store/lib/**/*dart') }}
-    - name: Setup dart
-      if: steps.cache-generated.outputs.cache-hit != 'true'
-      uses: dart-lang/setup-dart@v1
-      with:
-        sdk: ${{ env.DART_SDK }}
-    - name: Generate code
-      if: steps.cache-generated.outputs.cache-hit != 'true'
-      run: dart run build_runner build
-      working-directory: health_data_store
-    - name: Setup Flutter
-      uses: subosito/flutter-action@v2
-      with:
-        channel: ${{ env.FLUTTER_CHANNEL }}
-        cache: true
-    - name: Disable analytics
-      run: flutter config --no-analytics --suppress-analytics
-    - name: Generate mock code
-      run: |
-        flutter pub get
-        flutter pub run build_runner build
-      working-directory: app
-    - name: Run tests
-      run: flutter test --coverage
-      working-directory: app
-    - name: Update goldens
-      id: gold-upd
-      if: failure()
-      run: flutter test --update-goldens --fail-fast
-      working-directory: app
-    - name: PR golden changes
-      # https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/evaluate-expressions-in-workflows-and-actions#example-of-failure-with-conditions
-      if: ${{ failure() && steps.gold-upd.conclusion == 'success' }}
-      run: |
-        git config user.name "GitHub Action (update goldens)"
-        git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
-        
-        git checkout -B action-update-goldens
-        export STATUS=$(git status)
-        git commit -am "Update goldens"
-        git push --set-upstream origin action-update-goldens
-        
-        gh pr create \
-          --base main \
-          --head action-update-goldens \
-          --title "Update goldens" \
-          --body "$STATUS"
-      env:
-        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-
-#  Disabled: Integration tests are disabled as emulator setup fails on
-#  GH-actions. Actions images no longer provide enough disk space to create
-#  the userdata partition.
-#
-#  integration-test:
-#    name: "🛠️🧪 Run integration tests"
-#    runs-on: ubuntu-latest
-#
-#    steps:
-#      - name: Checkout code
-#        uses: actions/checkout@v4
-#        with:
-#          # ensures there are no unexpected directories needed
-#          sparse-checkout: |
-#            app
-#            health_data_store
-#      - name: Enable KVM group perms
-#        # see: https://github.com/actions/runner-images/discussions/7191
-#        run: |
-#          echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
-#          sudo udevadm control --reload-rules
-#          sudo udevadm trigger --name-match=kvm
-#      - name: Setup Java
-#        uses: actions/setup-java@v3
-#        with:
-#          distribution: 'zulu'
-#          java-version: ${{ env.JAVA_VERSION }}
-#
-#      - name: Download Android emulator image
-#        run: |
-#          export ANDROID_TOOLS="$ANDROID_HOME/cmdline-tools/latest/bin"
-#          echo "y" | $ANDROID_TOOLS/sdkmanager --install "${{ env.EMULATOR_VERSION }}"
-#          echo "no" | $ANDROID_TOOLS/avdmanager create avd --force --name emu -k '${{ env.EMULATOR_VERSION }}'
-#          echo "Android emulator installed"
-#          $ANDROID_HOME/emulator/emulator -list-avds
-#      - name: Setup dart
-#        uses: dart-lang/setup-dart@v1
-#        with:
-#          sdk: ${{ env.DART_SDK }}
-#      - name: Generate code
-#        run: dart run build_runner build
-#        working-directory: health_data_store
-#      - name: Setup Flutter
-#        uses: subosito/flutter-action@v2
-#        with:
-#          channel: ${{ env.FLUTTER_CHANNEL }}
-#      - name: Start Android emulator
-#        timeout-minutes: 10
-#        run: |
-#          export ANDROID_TOOLS="$ANDROID_HOME/cmdline-tools/latest/bin"
-#          echo "Starting emulator"
-#          $ANDROID_TOOLS/sdkmanager "platform-tools" "${{ env.EMULATOR_VERSION }}"
-#          nohup $ANDROID_HOME/emulator/emulator -avd emu -no-audio -no-snapshot -no-window &
-#          $ANDROID_HOME/platform-tools/adb wait-for-device shell 'while [[ -z $(getprop sys.boot_completed | tr -d '\r') ]]; do sleep 1; done; input keyevent 82'
-#          $ANDROID_HOME/platform-tools/adb devices
-#          echo "Android emulator started"
-#      - name: Run integration tests
-#        run: flutter test integration_test --flavor github
-#        working-directory: app
+      - name: Checkout code
+        uses: actions/checkout@v4
+        with:
+          # ensures there are no unexpected directories needed
+          sparse-checkout: |
+            app
+            health_data_store
+      - name: Cache generated health data store
+        id: cache-generated
+        uses: actions/cache@v4
+        with:
+          path: health_data_store/lib
+          key: builder-${{ matrix.channel }}-${{ hashFiles('health_data_store/pubspec.yaml', 'health_data_store/lib/*', 'health_data_store/lib/**/*dart') }}
+      - name: Setup dart
+        if: steps.cache-generated.outputs.cache-hit != 'true'
+        uses: dart-lang/setup-dart@v1
+        with:
+          sdk: ${{ matrix.channel }}
+      - name: Generate code
+        if: steps.cache-generated.outputs.cache-hit != 'true'
+        run: dart run build_runner build
+        working-directory: health_data_store
+      - name: Setup Flutter
+        uses: subosito/flutter-action@v2
+        with:
+          channel: ${{ matrix.channel }}
+          cache: true
+      - name: Disable analytics
+        run:
+          flutter config --no-analytics --suppress-analytics
+      - name: Update app dependencies
+        run: flutter pub get
+        working-directory: app
+      - name: Generate app mock code # no efficient caching possible
+        run: flutter pub run build_runner build
+        working-directory: app
+      - name: Run tests
+        run: flutter test --coverage --dart-define="channel=${{ matrix.channel }}"
+        working-directory: app
+      - name: Update goldens
+        id: gold-upd
+        if: failure()
+        run: flutter test --update-goldens --fail-fast --dart-define="channel=${{ matrix.channel }}"
+        working-directory: app
+      - name: PR golden changes
+        # https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/evaluate-expressions-in-workflows-and-actions#example-of-failure-with-conditions
+        if: ${{ failure() && steps.gold-upd.conclusion == 'success' }}
+        run: |
+          git config user.name "GitHub Action (update goldens)"
+          git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
 
-  build-android:
-    name: "🛠️ Build Android"
-    runs-on: ubuntu-latest
+          git checkout -B action-update-goldens
+          export STATUS=$(git status)
+          git commit -am "Update goldens"
+          git push --set-upstream origin action-update-goldens
 
-    steps:
-    - name: Checkout code
-      uses: actions/checkout@v4
-      with:
-        # ensures there are no unexpected directories needed
-        sparse-checkout: |
-          app
-          health_data_store
-    - name: Cache generated health data store
-      id: cache-generated
-      uses: actions/cache@v4
-      with:
-        path: health_data_store/lib
-        key: builder-${{ hashFiles('health_data_store/pubspec.yaml', 'health_data_store/lib/*', 'health_data_store/lib/**/*dart') }}
-    - name: Setup dart
-      if: steps.cache-generated.outputs.cache-hit != 'true'
-      uses: dart-lang/setup-dart@v1
-      with:
-        sdk: ${{ env.DART_SDK }}
-    - name: Generate code
-      if: steps.cache-generated.outputs.cache-hit != 'true'
-      run: dart run build_runner build
-      working-directory: health_data_store
-    - name: Setup Flutter
-      uses: subosito/flutter-action@v2
-      with:
-        channel: ${{ env.FLUTTER_CHANNEL }}
-        cache: true
-    - name: Disable analytics
-      run: flutter config --no-analytics --suppress-analytics
-    - name: Setup java
-      uses: actions/setup-java@v2
-      with:
-        java-version: "17"
-        distribution: "temurin"
-        cache: 'gradle'
-    - name: Build apk
-      run: flutter build apk --flavor github --debug
-      working-directory: app
+          gh pr create \
+            --base main \
+            --head action-update-goldens \
+            --title "Update goldens" \
+            --body "$STATUS"
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
\ No newline at end of file
.github/workflows/extendend-testing.yml
@@ -0,0 +1,229 @@
+name: 'Extended testing'
+
+on:
+  workflow_dispatch:
+
+jobs:
+  setup-env:
+    name: "Setup environment"
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        branch:
+          - beta
+          - stable
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v4
+        with:
+          # ensures there are no unexpected directories needed
+          sparse-checkout: |
+            app
+            health_data_store
+      - name: Cache generated health data store
+        id: cache-generated
+        uses: actions/cache@v4
+        with:
+          path: health_data_store/lib
+          key: builder-${{ matrix.branch }}-${{ hashFiles('health_data_store/pubspec.yaml', 'health_data_store/lib/*', 'health_data_store/lib/**/*dart') }}
+      - name: Setup dart
+        if: steps.cache-generated.outputs.cache-hit != 'true'
+        uses: dart-lang/setup-dart@v1
+        with:
+          sdk: ${{ matrix.branch }}
+      - name: Generate code
+        if: steps.cache-generated.outputs.cache-hit != 'true'
+        run: dart run build_runner build
+        working-directory: health_data_store
+      - name: Setup Flutter
+        uses: subosito/flutter-action@v2
+        with:
+          channel: ${{ matrix.branch }}
+          cache: true
+      - name: Disable analytics
+        run:
+          flutter config --no-analytics --suppress-analytics
+      - name: Update app dependencies
+        run: flutter pub get
+        working-directory: app
+      - name: Generate app mock code
+        run: flutter pub run build_runner build
+        working-directory: app
+      - name: Upload results
+        uses: actions/upload-artifact@v4
+        with:
+          name: src-${{ matrix.branch }}
+          path: ./
+
+  unit-test:
+    name: "🧩🧪 Run unit tests"
+    runs-on: ubuntu-latest
+    needs: setup-env
+    strategy:
+      matrix:
+        branch:
+          - beta
+          - stable
+    permissions:
+      contents: write
+      pull-requests: write
+    steps:
+      - uses: actions/checkout@v4
+        with:
+          sparse-checkout: |
+            app
+            health_data_store
+      - name: Download src directory
+        uses: actions/download-artifact@v4
+        with:
+          name: src-${{ matrix.branch }}
+      - name: Setup Flutter
+        uses: subosito/flutter-action@v2
+        with:
+          channel: ${{ matrix.branch }}
+          cache: true
+      - name: Disable analytics
+        run: flutter config --no-analytics --suppress-analytics
+      - name: Run tests ignoring goldens
+        run: flutter test --coverage --update-goldens
+        working-directory: app
+
+#  Disabled: Integration tests are disabled as emulator setup fails on
+#  GH-actions. Actions images no longer provide enough disk space to create
+#  the userdata partition.
+#
+#  integration-test:
+#    name: "🛠️🧪 Run integration tests"
+#    runs-on: ubuntu-latest
+#
+#    steps:
+#      - name: Checkout code
+#        uses: actions/checkout@v4
+#        with:
+#          # ensures there are no unexpected directories needed
+#          sparse-checkout: |
+#            app
+#            health_data_store
+#      - name: Enable KVM group perms
+#        # see: https://github.com/actions/runner-images/discussions/7191
+#        run: |
+#          echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
+#          sudo udevadm control --reload-rules
+#          sudo udevadm trigger --name-match=kvm
+#      - name: Setup Java
+#        uses: actions/setup-java@v3
+#        with:
+#          distribution: 'zulu'
+#          java-version: ${{ env.JAVA_VERSION }}
+#
+#      - name: Download Android emulator image
+#        run: |
+#          export ANDROID_TOOLS="$ANDROID_HOME/cmdline-tools/latest/bin"
+#          echo "y" | $ANDROID_TOOLS/sdkmanager --install "${{ env.EMULATOR_VERSION }}"
+#          echo "no" | $ANDROID_TOOLS/avdmanager create avd --force --name emu -k '${{ env.EMULATOR_VERSION }}'
+#          echo "Android emulator installed"
+#          $ANDROID_HOME/emulator/emulator -list-avds
+#      - name: Setup dart
+#        uses: dart-lang/setup-dart@v1
+#        with:
+#          sdk: ${{ env.DART_SDK }}
+#      - name: Generate code
+#        run: dart run build_runner build
+#        working-directory: health_data_store
+#      - name: Setup Flutter
+#        uses: subosito/flutter-action@v2
+#        with:
+#          channel: ${{ env.FLUTTER_CHANNEL }}
+#      - name: Start Android emulator
+#        timeout-minutes: 10
+#        run: |
+#          export ANDROID_TOOLS="$ANDROID_HOME/cmdline-tools/latest/bin"
+#          echo "Starting emulator"
+#          $ANDROID_TOOLS/sdkmanager "platform-tools" "${{ env.EMULATOR_VERSION }}"
+#          nohup $ANDROID_HOME/emulator/emulator -avd emu -no-audio -no-snapshot -no-window &
+#          $ANDROID_HOME/platform-tools/adb wait-for-device shell 'while [[ -z $(getprop sys.boot_completed | tr -d '\r') ]]; do sleep 1; done; input keyevent 82'
+#          $ANDROID_HOME/platform-tools/adb devices
+#          echo "Android emulator started"
+#      - name: Run integration tests
+#        run: flutter test integration_test --flavor github
+#        working-directory: app
+
+  build-android:
+    name: "🛠️ Build Android"
+    strategy:
+      matrix:
+        java-version:
+          - 17
+          - 21
+        branch:
+          - beta
+          - stable
+        build:
+          - debug
+          - release
+
+        flavor:
+          - github
+          - fdroid
+    runs-on: ubuntu-latest
+    needs: setup-env
+    steps:
+      - name: Download src directory
+        uses: actions/download-artifact@v4
+        with:
+          name: src-${{ matrix.branch }}
+      - name: Setup Flutter
+        uses: subosito/flutter-action@v2
+        with:
+          channel: ${{ matrix.branch }}
+          cache: true
+      - name: Disable analytics
+        run: flutter config --no-analytics --suppress-analytics
+      - name: Setup java
+        uses: actions/setup-java@v2
+        with:
+          java-version: "17"
+          distribution: "temurin"
+          cache: 'gradle'
+      - name: Build apk
+        run: flutter build apk --flavor ${{ matrix.flavor }} --${{ matrix.build }}
+        working-directory: app
+      - name: Upload apks
+        uses: actions/upload-artifact@v4
+        with:
+          name: app-${{ matrix.build }}-java${{ matrix.java-version }}-flutter${{ matrix.branch }}
+          path: ./app/build/app/outputs/flutter-apk/*.apk
+  build-linux:
+    name: "🖥️ Build Desktop (linux)"
+    strategy:
+      matrix:
+        branch:
+          - beta
+          - stable
+        build:
+          - debug
+          - release
+
+    runs-on: ubuntu-latest
+    needs: setup-env
+    steps:
+      - name: Download src directory
+        uses: actions/download-artifact@v4
+        with:
+          name: src-${{ matrix.branch }}
+      - name: Setup Flutter
+        uses: subosito/flutter-action@v2
+        with:
+          channel: ${{ matrix.branch }}
+          cache: true
+      - name: Disable analytics
+        run: flutter config --no-analytics --suppress-analytics
+      - name: Build linux
+        run: flutter build linux --${{ matrix.build }}
+        working-directory: app
+      - name: Upload program
+        uses: actions/upload-artifact@v4
+        with:
+          name: linux-${{ matrix.build }}-flutter${{ matrix.branch }}-
+          path: ./app/build/linux/x64/release/bundle/blood_pressure_app/
+
app/lib/model/iso_lang_names.dart
@@ -19,7 +19,7 @@ String getDisplayLanguage(Locale l) => switch(l.toLanguageTag()) {
   'hu' => 'Magyar (Magyarország)',
   'et' => 'Eesti (Eesti)',
   'nl' => 'Nederlands',
-  'cs' => 'čeština',
+  'cs' => 'Čeština',
   // Websites with names for expanding when new languages get added:
   // - https://chronoplexsoftware.com/localisation/help/languagecodes.htm
   // - https://localizely.com/locale-code/zh-Hans/
app/test/features/statistics/beta-ClockBpGraph-dark.png
Binary file
app/test/features/statistics/beta-ClockBpGraph-light.png
Binary file
app/test/features/statistics/beta-full_graph-years.png
Binary file
app/test/features/statistics/value-graph-end-warn.png → app/test/features/statistics/beta-value-graph-end-warn.png
File renamed without changes
app/test/features/statistics/value-graph-start-warn.png → app/test/features/statistics/beta-value-graph-start-warn.png
File renamed without changes
app/test/features/statistics/clock_bp_graph_test.dart
@@ -7,6 +7,7 @@ import 'package:flutter_test/flutter_test.dart';
 import 'package:provider/provider.dart';
 
 import '../../model/analyzer_test.dart';
+import '../../util.dart';
 
 void main() {
   testWidgets("doesn't throw when empty" , (tester) async {
@@ -41,7 +42,7 @@ void main() {
         ),
       ),
     ));
-    await expectLater(find.byType(ClockBpGraph), matchesGoldenFile('ClockBpGraph-light.png'));
+    await expectLater(find.byType(ClockBpGraph), myMatchesGoldenFile('ClockBpGraph-light.png'));
   });
   testWidgets('renders sample data like expected in dart mode', (tester) async {
     final rng = Random(1234);
@@ -64,6 +65,6 @@ void main() {
         ),
       ),
     ));
-    await expectLater(find.byType(ClockBpGraph), matchesGoldenFile('ClockBpGraph-dark.png'));
+    await expectLater(find.byType(ClockBpGraph), myMatchesGoldenFile('ClockBpGraph-dark.png'));
   });
 }
app/test/features/statistics/ClockBpGraph-dark.png
Binary file
app/test/features/statistics/ClockBpGraph-light.png
Binary file
app/test/features/statistics/full_graph-years.png
Binary file
app/test/features/statistics/stable-ClockBpGraph-dark.png
Binary file
app/test/features/statistics/stable-ClockBpGraph-light.png
Binary file
app/test/features/statistics/stable-full_graph-years.png
Binary file
app/test/features/statistics/stable-value-graph-end-warn.png
Binary file
app/test/features/statistics/stable-value-graph-start-warn.png
Binary file
app/test/features/statistics/value_graph_test.dart
@@ -100,7 +100,7 @@ void main() {
     final localizations = await AppLocalizations.delegate.load(const Locale('en'));
     expect(find.text(localizations.errNotEnoughDataToGraph), findsNothing);
 
-    await expectLater(find.byType(BloodPressureValueGraph), matchesGoldenFile('full_graph-years.png'));
+    await expectLater(find.byType(BloodPressureValueGraph), myMatchesGoldenFile('full_graph-years.png'));
   });
 
   testWidgets('BloodPressureValueGraph is fine with enough values in sys category', (tester) async {
@@ -142,7 +142,7 @@ void main() {
     final localizations = await AppLocalizations.delegate.load(const Locale('en'));
     expect(find.text(localizations.errNotEnoughDataToGraph), findsNothing);
 
-    await expectLater(find.byType(BloodPressureValueGraph), matchesGoldenFile('value-graph-start-warn.png'));
+    await expectLater(find.byType(BloodPressureValueGraph), myMatchesGoldenFile('value-graph-start-warn.png'));
   });
   testWidgets('graph renders area at end correctly', (tester) async {
     await tester.pumpWidget(_buildGraph([
@@ -158,7 +158,7 @@ void main() {
     final localizations = await AppLocalizations.delegate.load(const Locale('en'));
     expect(find.text(localizations.errNotEnoughDataToGraph), findsNothing);
 
-    await expectLater(find.byType(BloodPressureValueGraph), matchesGoldenFile('value-graph-end-warn.png'));
+    await expectLater(find.byType(BloodPressureValueGraph), myMatchesGoldenFile('value-graph-end-warn.png'));
   });
 }
 
app/test/util.dart
@@ -327,3 +327,8 @@ class MockMedicineIntakeRepository extends _MockRepo<MedicineIntake> implements
 class MockMedicineRepository extends _MockRepo<Medicine> implements MedicineRepository {}
 class MockNoteRepository extends _MockRepo<Note> implements NoteRepository {}
 class MockBodyweightRepository extends _MockRepo<BodyweightRecord> implements BodyweightRepository {}
+
+dynamic myMatchesGoldenFile(String key) {
+  final channel = const String.fromEnvironment('channel');
+  return matchesGoldenFile('$channel-$key');
+}