import 'dart:convert'; import 'dart:io'; import 'package:pipe/pipe.dart'; import 'package:test/test.dart'; /// Contract for the experimental C++ front end (tool/cpp_front.py): it writes a /// state model, and the unchanged rules must fire on each positive control in /// test/fixtures/cpp_state and stay silent on each negative one. /// /// Skipped, not passed, when libclang (plus its Python bindings) or python3 is /// unavailable. /// /// Discovery mirrors tool/cpp_front.py deliberately. This test used to look /// only for Homebrew's `libclang.dylib`, so on Linux it skipped rather than /// ran -- which is how the front end came to be unusable there without any /// suite noticing. String _llvmPrefix() { final env = Platform.environment['PIPE_LLVM']; if (env != null && env.isNotEmpty) return env; return Platform.isMacOS ? '/opt/homebrew/opt/llvm' : '/usr'; } /// The library, by platform and possibly versioned: Linux distributions ship /// `libclang.so..` and do not always add the unversioned /// symlink, and Debian/Ubuntu keep it under a multiarch subdirectory. bool _libclangPresent(String prefix) { final suffix = Platform.isMacOS ? 'dylib' : 'so'; final roots = [Directory('$prefix/lib')]; final libDir = Directory('$prefix/lib'); if (libDir.existsSync()) { roots.addAll( libDir.listSync().whereType().where( (d) => d.path.endsWith('-linux-gnu'), ), ); } for (final root in roots) { if (!root.existsSync()) continue; if (File('${root.path}/libclang.$suffix').existsSync()) return true; final versioned = root.listSync().whereType().any( (f) => f.uri.pathSegments.last.startsWith('libclang.$suffix'), ); if (versioned) return true; } return false; } void main() { final llvm = _llvmPrefix(); final available = _libclangPresent(llvm) && Process.runSync('python3', ['--version']).exitCode == 0; late FindingReport report; setUpAll(() async { if (!available) return; final root = Directory.systemTemp.createTempSync('pipe-cpp'); Directory('${root.path}/src').createSync(); File( 'test/fixtures/cpp_state/src/engine.cpp', ).copySync('${root.path}/src/engine.cpp'); File('${root.path}/compile_commands.json').writeAsStringSync( jsonEncode([ { 'directory': root.path, 'file': 'src/engine.cpp', 'arguments': ['c++', '-std=c++20', '-c', 'src/engine.cpp'], }, ]), ); final model = '${root.path}/model.json'; final result = Process.runSync('python3', [ 'tool/cpp_front.py', root.path, '--include', 'src', '-o', model, ]); expect(result.exitCode, 0, reason: '${result.stderr}'); expect('${result.stderr}', contains('0 clang error(s)')); report = runChecks( flow: stateFlowFromJson( jsonDecode(File(model).readAsStringSync()) as Map, ), ); }); Iterable hits(String rule) => report.findings.where((finding) => finding.rule == rule); test('an enum produced but never examined is reported', () { expect( hits(ruleEmittedNotRendered).map((f) => f.data['union']), contains('fx::Unread'), ); }, skip: available ? false : 'libclang / python3 not available'); test('an enumerator examined but never produced is a dead branch', () { final ghost = hits( ruleRenderedNotEmitted, ).where((f) => f.data['variant'] == 'Ghost'); expect(ghost, hasLength(1)); expect(ghost.single.file, 'src/engine.cpp'); expect(ghost.single.line, isNotNull); }, skip: available ? false : 'libclang / python3 not available'); test('a cast to one enum does not mute "never emitted" for another', () { // Casted::Y is produced only through static_cast, so it is unprovable // and suppressed. Phase lives in the same class and must still be judged. expect( hits(ruleRenderedNotEmitted).map((f) => f.data['union']), isNot(contains('fx::Casted')), ); expect(report.suppressed.where((s) => s.contains('fx::Phase')), isEmpty); }, skip: available ? false : 'libclang / python3 not available'); test( 'a swallowed failure on the method\'s own busy state fires, and only it', () { // startGuarded resolves the state, startNested's try is a nested local // fallback, and Reporter::run sets a field of a local event object. expect(hits(ruleLoadingNotReset).map((f) => f.data['member']).toList(), [ 'startSwallowed', ]); }, skip: available ? false : 'libclang / python3 not available', ); }