Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions testing/run_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,22 @@ def RunClangTidyTests(build_dir):
cwd=test_dir)


def RunApiConsistencyTests(build_dir):
test_dir = os.path.join(buildroot_dir, 'flutter', 'tools', 'api_check')
dart_tests = glob.glob('%s/test/*_test.dart' % test_dir)
for dart_test_file in dart_tests:
opts = [
'--disable-dart-dev',
dart_test_file,
os.path.join(buildroot_dir, 'flutter')]
RunEngineExecutable(
build_dir,
os.path.join('dart-sdk', 'bin', 'dart'),
None,
flags=opts,
cwd=test_dir)


def main():
parser = argparse.ArgumentParser()
all_types = ['engine', 'dart', 'benchmarks', 'java', 'android', 'objc', 'font-subset']
Expand Down Expand Up @@ -611,6 +627,7 @@ def main():
RunLitetestTests(build_dir)
RunGithooksTests(build_dir)
RunClangTidyTests(build_dir)
RunApiConsistencyTests(build_dir)
RunDartTests(build_dir, dart_filter, args.verbose_dart_snapshot)
RunConstFinderTests(build_dir)
RunFrontEndServerTests(build_dir)
Expand Down
18 changes: 18 additions & 0 deletions tools/api_check/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# API consistency check tool

Verifies that enums in each of the platform-specific embedders, and the embedder
API remain consistent with their API in dart:ui.

### Running the tool

This tool is run as part of `testing/run_tests.sh`.

To run the tool, invoke with the path of the Flutter engine repo as the first
argument.

```
../../../out/host_debug_unopt/dart-sdk/bin/dart \
--disable-dart-dev \
test/apicheck_test.dart \
"$(dirname $(dirname $PWD))"
```
158 changes: 158 additions & 0 deletions tools/api_check/lib/apicheck.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:convert' show LineSplitter;
import 'dart:io';

import 'package:analyzer/dart/analysis/analysis_context_collection.dart';
import 'package:analyzer/dart/analysis/analysis_context.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/analysis/session.dart';
import 'package:analyzer/dart/ast/ast.dart';

/// Returns all indexed fields in [className].
///
/// Field names are expected to be of the form `kFooBarIndex`; prefixed with a
/// `k` and terminated in `Index`.
List<String> getDartClassFields({
required String sourcePath,
required String className,
}) {
final List<String> includedPaths = <String>[sourcePath];
final AnalysisContextCollection collection = AnalysisContextCollection(includedPaths: includedPaths);
final AnalysisContext context = collection.contextFor(sourcePath);
final AnalysisSession session = context.currentSession;

final result = session.getParsedUnit(sourcePath);
if (result is! ParsedUnitResult) {
return <String>[];
}

// Locate all fields matching the expression in the class.
final RegExp fieldExp = RegExp(r'_k(\w*)Index');
final List<String> fields = <String>[];
for (CompilationUnitMember unitMember in result.unit.declarations) {
if (unitMember is ClassDeclaration && unitMember.name.name == className) {
for (ClassMember classMember in unitMember.members) {
if (classMember is FieldDeclaration) {
for (VariableDeclaration field in classMember.fields.variables) {
final String fieldName = field.name.name;
final RegExpMatch? match = fieldExp.firstMatch(fieldName);
if (match != null) {
fields.add(match.group(1)!);
}
}
}
}
}
}
return fields;
}

/// Returns all values in [enumName].
///
/// Enum values are expected to be of the form `kEnumNameFooBar`; prefixed with
/// `kEnumName`.
List<String> getCppEnumValues({
required String sourcePath,
required String enumName,
}) {
List<String> lines = File(sourcePath).readAsLinesSync();
final int enumEnd = lines.indexOf('} $enumName;');
if (enumEnd < 0) {
return <String>[];
}
final int enumStart = lines.lastIndexOf('typedef enum {', enumEnd);
if (enumStart < 0 || enumStart >= enumEnd) {
return <String>[];
}
final valueExp = RegExp('^\\s*k$enumName(\\w*)');
return _extractMatchingExpression(
lines: lines.sublist(enumStart + 1, enumEnd),
regexp: valueExp,
);
}

/// Returns all values in [enumName].
///
/// Enum values are expected to be of the form `kFooBar`; prefixed with `k`.
List<String> getCppEnumClassValues({
required String sourcePath,
required String enumName,
}) {
final List<String> lines = _getBlockStartingWith(
source: File(sourcePath).readAsStringSync(),
startExp: RegExp('enum class $enumName .* {'),
);
final valueExp = RegExp(r'^\s*k(\w*)');
return _extractMatchingExpression(lines: lines, regexp: valueExp);
}

/// Returns all values in [enumName].
///
/// Enum value declarations are expected to be of the form `FOO_BAR(1 << N)`;
/// in all caps.
List<String> getJavaEnumValues({
required String sourcePath,
required String enumName,
}) {
final List<String> lines = _getBlockStartingWith(
source: File(sourcePath).readAsStringSync(),
startExp: RegExp('enum $enumName {'),
);
final RegExp valueExp = RegExp(r'^\s*([A-Z_]*)\(');
return _extractMatchingExpression(lines: lines, regexp: valueExp);
}

/// Returns all values in [lines] whose line of code matches [regexp].
///
/// The contents of the first match group in [regexp] is returned; therefore
/// it must contain a match group.
List<String> _extractMatchingExpression({
required Iterable<String> lines,
required RegExp regexp,
}) {
List<String> values = <String>[];
for (String line in lines) {
final RegExpMatch? match = regexp.firstMatch(line);
if (match != null) {
values.add(match.group(1)!);
}
}
return values;
}

/// Returns all lines of the block starting with [startString].
///
/// [startString] MUST end with '{'.
List<String> _getBlockStartingWith({
required String source,
required RegExp startExp,
}) {
assert(startExp.pattern.endsWith('{'));

final int blockStart = source.indexOf(startExp);
if (blockStart < 0) {
return <String>[];
}
// Find start of block.
int pos = blockStart;
while (pos < source.length && source[pos] != '{') {
pos++;
}
int braceCount = 1;

// Count braces until end of block.
pos++;
while (pos < source.length && braceCount > 0) {
if (source[pos] == '{') {
braceCount++;
} else if (source[pos] == '}') {
braceCount--;
}
pos++;
}
final int blockEnd = pos;
return LineSplitter.split(source, blockStart, blockEnd).toList();
}
84 changes: 84 additions & 0 deletions tools/api_check/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Copyright 2013 The Flutter Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

name: apicheck
publish_to: none

# Do not add any dependencies that require more than what is provided in
# //third_party/dart/pkg or //third_party/dart/third_party/pkg.
# In particular, package:test is not usable here.

# If you do add packages here, make sure you can run `pub get --offline`, and
# check the .packages and .package_config to make sure all the paths are
# relative to this directory into //third_party/dart

environment:
sdk: '>=2.12.0 <3.0.0'

# Do not add any dependencies that require more than what is provided in
# //third_party/pkg, //third_party/dart/pkg, or
# //third_party/dart/third_party/pkg. In particular, package:test is not usable
# here.

# If you do add packages here, make sure you can run `pub get --offline`, and
# check the .packages and .package_config to make sure all the paths are
# relative to this directory into //third_party/dart

dependencies:
analyzer: any
_fe_analyzer_shared: any

dev_dependencies:
async_helper: any
expect: any
litetest: any
smith: any

dependency_overrides:
_fe_analyzer_shared:
path: ../../../third_party/dart/pkg/_fe_analyzer_shared
analyzer:
path: ../../../third_party/dart/pkg/analyzer
async:
path: ../../../third_party/dart/third_party/pkg/async
async_helper:
path: ../../../third_party/dart/pkg/async_helper
charcode:
path: ../../../third_party/dart/third_party/pkg/charcode
collection:
path: ../../../third_party/dart/third_party/pkg/collection
convert:
path: ../../../third_party/dart/third_party/pkg/convert
crypto:
path: ../../../third_party/dart/third_party/pkg/crypto
expect:
path: ../../../third_party/dart/pkg/expect
file:
path: ../../../third_party/pkg/file/packages/file
glob:
path: ../../../third_party/dart/third_party/pkg/glob
litetest:
path: ../../testing/litetest
meta:
path: ../../../third_party/dart/pkg/meta
package_config:
path: ../../../third_party/dart/third_party/pkg_tested/package_config
path:
path: ../../../third_party/dart/third_party/pkg/path
pub_semver:
path: ../../../third_party/dart/third_party/pkg/pub_semver
source_span:
path: ../../../third_party/dart/third_party/pkg/source_span
string_scanner:
path: ../../../third_party/dart/third_party/pkg/string_scanner
term_glyph:
path: ../../../third_party/dart/third_party/pkg/term_glyph
typed_data:
path: ../../../third_party/dart/third_party/pkg/typed_data
watcher:
path: ../../../third_party/dart/third_party/pkg/watcher
yaml:
path: ../../../third_party/dart/third_party/pkg/yaml
smith:
path: ../../../third_party/dart/pkg/smith
Loading