[ffigen] Update clang to fix CI failure (#425)

* Use a special matcher for diffing large strings.

* fmt

* fmt

* Fix CI

* Use more recent version of linux

So that we can use a more recent clang

* Try ubuntu-22.04
diff --git a/pkgs/ffigen/.github/workflows/test-package.yml b/pkgs/ffigen/.github/workflows/test-package.yml
index 672f339..6d6c470 100644
--- a/pkgs/ffigen/.github/workflows/test-package.yml
+++ b/pkgs/ffigen/.github/workflows/test-package.yml
@@ -39,8 +39,7 @@
 
   test-linux:
     needs: analyze
-    # This job requires clang-10 which is the default on 20.04
-    runs-on: ubuntu-20.04
+    runs-on: ubuntu-22.04
     steps:
       - uses: actions/checkout@v2
       - uses: dart-lang/setup-dart@v1.0
@@ -48,8 +47,8 @@
           sdk: stable
       - name: Install dependencies
         run: dart pub get
-      - name: Install libclang-10-dev
-        run: sudo apt-get install libclang-10-dev
+      - name: Install libclang-14-dev
+        run: sudo apt-get install libclang-14-dev
       - name: Build test dylib and bindings
         run: dart test/setup.dart
       - name: Run VM tests
diff --git a/pkgs/ffigen/pubspec.yaml b/pkgs/ffigen/pubspec.yaml
index 400fdd3..dff5023 100644
--- a/pkgs/ffigen/pubspec.yaml
+++ b/pkgs/ffigen/pubspec.yaml
@@ -23,4 +23,5 @@
 
 dev_dependencies:
   lints: ^1.0.1
+  list_diff: ^2.0.1
   test: ^1.16.2
diff --git a/pkgs/ffigen/test/large_integration_tests/_expected_libclang_bindings.dart b/pkgs/ffigen/test/large_integration_tests/_expected_libclang_bindings.dart
index 53e3a65..d060bd4 100644
--- a/pkgs/ffigen/test/large_integration_tests/_expected_libclang_bindings.dart
+++ b/pkgs/ffigen/test/large_integration_tests/_expected_libclang_bindings.dart
@@ -3962,8 +3962,8 @@
 
   /// Given a cursor that represents an Objective-C method or property
   /// declaration, return non-zero if the declaration was affected by
-  /// "@optional". Returns zero if the cursor is not such a declaration or it is
-  /// "@required".
+  /// "\@optional". Returns zero if the cursor is not such a declaration or it
+  /// is "\@required".
   int clang_Cursor_isObjCOptional(
     CXCursor C,
   ) {
diff --git a/pkgs/ffigen/test/test_utils.dart b/pkgs/ffigen/test/test_utils.dart
index 314f8aa..fef4c4c 100644
--- a/pkgs/ffigen/test/test_utils.dart
+++ b/pkgs/ffigen/test/test_utils.dart
@@ -5,6 +5,7 @@
 import 'dart:io';
 
 import 'package:ffigen/src/code_generator.dart';
+import 'package:list_diff/list_diff.dart' as list_diff;
 import 'package:logging/logging.dart';
 import 'package:path/path.dart' as path;
 import 'package:test/test.dart';
@@ -40,6 +41,43 @@
   }
 }
 
+// String matcher for large strings, which prints the diffs between the expected
+// and actual values, rather than just printing the entire strings.
+class _LargeStringMatcher extends Matcher {
+  final String expected;
+
+  _LargeStringMatcher(this.expected);
+
+  @override
+  bool matches(dynamic item, Map matchState) {
+    return expected == item;
+  }
+
+  @override
+  Description describe(Description description) {
+    return description.add('Has no diffs');
+  }
+
+  @override
+  Description describeMismatch(Object? item, Description mismatchDescription,
+      Map matchState, bool verbose) {
+    if (item is! String) {
+      return mismatchDescription.add('is ').addDescriptionOf(item);
+    }
+    return mismatchDescription.add('has diff:\n').add(_diff(item));
+  }
+
+  String _diff(String actual) {
+    final out = StringBuffer();
+    for (final op
+        in list_diff.diffSync(expected.split('\n'), actual.split('\n'))) {
+      out.write(op.isInsertion ? 'ACT ' : 'EXP ');
+      out.write('${op.index}\t${op.item}\n');
+    }
+    return out.toString();
+  }
+}
+
 /// Generates actual file using library and tests using [expect] with expected
 ///
 /// This will not delete the actual debug file incase [expect] throws an error.
@@ -55,7 +93,7 @@
     final expected = File(path.joinAll(pathToExpected))
         .readAsStringSync()
         .replaceAll('\r', '');
-    expect(actual, expected);
+    expect(actual, _LargeStringMatcher(expected));
     if (file.existsSync()) {
       file.delete();
     }