diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml index c3e7d1c..9593e13 100644 --- a/.github/workflows/dart.yml +++ b/.github/workflows/dart.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - sdk: [dev, 2.17.0] + sdk: [3.0.0, dev] steps: - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f @@ -60,7 +60,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - sdk: [dev, 2.17.0] + sdk: [3.0.0, dev] platform: [vm, chrome] exclude: # We only run Chrome tests on Linux. No need to run them @@ -92,3 +92,6 @@ jobs: run: dart pub get - name: Run tests run: dart test --platform ${{ matrix.platform }} + - name: Run vmservice test + if: ${{ matrix.platform != 'chrome' }} + run: dart run --enable-vm-service --timeline-streams=Dart test/timeline_test.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index e67cd03..ad66e0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ * Add a `channelShutdownHandler` argument to `ClientChannel` and the subclasses. This callback can be used to react to channel shutdown or termination. * Export the `Code` protobuf enum from the `grpc.dart` library. +* Require Dart 3.0.0 or greater. ## 3.1.0 diff --git a/lib/src/client/call.dart b/lib/src/client/call.dart index 0cdda56..f27fe44 100644 --- a/lib/src/client/call.dart +++ b/lib/src/client/call.dart @@ -369,8 +369,10 @@ class ClientCall implements Response { if (!_headers.isCompleted) { _headerMetadata = data.metadata; if (_requestTimeline != null) { - _responseTimeline = timelineTaskFactory( - parent: _requestTimeline, filterKey: clientTimelineFilterKey); + _responseTimeline = TimelineTask( + parent: _requestTimeline, + filterKey: clientTimelineFilterKey, + ); } _responseTimeline?.start('gRPC Response'); _responseTimeline?.instant('Metadata received', arguments: { diff --git a/lib/src/client/channel.dart b/lib/src/client/channel.dart index bbc1461..4c5b99a 100644 --- a/lib/src/client/channel.dart +++ b/lib/src/client/channel.dart @@ -14,6 +14,7 @@ // limitations under the License. import 'dart:async'; +import 'dart:developer'; import '../shared/profiler.dart'; import '../shared/status.dart'; @@ -109,7 +110,7 @@ abstract class ClientChannelBase implements ClientChannel { requests, options, isTimelineLoggingEnabled - ? timelineTaskFactory(filterKey: clientTimelineFilterKey) + ? TimelineTask(filterKey: clientTimelineFilterKey) : null); getConnection().then((connection) { if (call.isCancelled) return; diff --git a/lib/src/client/common.dart b/lib/src/client/common.dart index 8a7d332..5060fc1 100644 --- a/lib/src/client/common.dart +++ b/lib/src/client/common.dart @@ -76,7 +76,7 @@ class ResponseStream extends StreamView with _ResponseMixin { ResponseFuture get single => ResponseFuture(_call); } -abstract class _ResponseMixin implements Response { +mixin _ResponseMixin implements Response { ClientCall get _call; @override diff --git a/lib/src/client/transport/web_streams.dart b/lib/src/client/transport/web_streams.dart index 3c0b074..aba0a7b 100644 --- a/lib/src/client/transport/web_streams.dart +++ b/lib/src/client/transport/web_streams.dart @@ -38,7 +38,7 @@ class GrpcWebDecoder extends Converter { } } -class _GrpcWebConversionSink extends ChunkedConversionSink { +class _GrpcWebConversionSink implements ChunkedConversionSink { static const int frameTypeData = 0x00; static const int frameTypeTrailers = 0x80; diff --git a/lib/src/shared/message.dart b/lib/src/shared/message.dart index 8b48fb0..d9d64db 100644 --- a/lib/src/shared/message.dart +++ b/lib/src/shared/message.dart @@ -41,7 +41,7 @@ class GrpcData extends GrpcMessage { String toString() => 'gRPC Data (${data.length} bytes)'; } -class GrpcMessageSink extends Sink { +class GrpcMessageSink implements Sink { late final GrpcMessage message; bool _messageReceived = false; diff --git a/lib/src/shared/profiler.dart b/lib/src/shared/profiler.dart index c229e11..0ce5d7a 100644 --- a/lib/src/shared/profiler.dart +++ b/lib/src/shared/profiler.dart @@ -13,17 +13,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -import 'dart:developer'; - -typedef TimelineTaskFactory = TimelineTask Function( - {String? filterKey, TimelineTask? parent}); - -TimelineTaskFactory timelineTaskFactory = _defaultTimelineTaskFactory; - -TimelineTask _defaultTimelineTaskFactory( - {String? filterKey, TimelineTask? parent}) => - TimelineTask(filterKey: filterKey, parent: parent); - const String clientTimelineFilterKey = 'grpc/client'; /// Enable logging requests and response for clients. diff --git a/lib/src/shared/streams.dart b/lib/src/shared/streams.dart index 6a8e3b7..6c4469e 100644 --- a/lib/src/shared/streams.dart +++ b/lib/src/shared/streams.dart @@ -59,7 +59,8 @@ class GrpcHttpDecoder extends Converter { } } -class _GrpcMessageConversionSink extends ChunkedConversionSink { +class _GrpcMessageConversionSink + implements ChunkedConversionSink { final Sink _out; final bool _forResponse; diff --git a/pubspec.yaml b/pubspec.yaml index 9fab7fa..b184713 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -6,7 +6,7 @@ version: 3.2.0-dev repository: https://github.com/grpc/grpc-dart environment: - sdk: '>=2.17.0 <3.0.0' + sdk: '>=3.0.0 <4.0.0' dependencies: archive: ^3.0.0 @@ -28,6 +28,7 @@ dev_dependencies: test: ^1.16.0 stream_channel: ^2.1.0 stream_transform: ^2.0.0 + vm_service: ^11.6.0 false_secrets: - interop/server1.key diff --git a/test/timeline_test.dart b/test/timeline_test.dart index c4123a1..c429198 100644 --- a/test/timeline_test.dart +++ b/test/timeline_test.dart @@ -14,16 +14,18 @@ // limitations under the License. @TestOn('vm') +@Skip( + 'Run only as `dart run --enable-vm-service --timeline-streams=Dart test/timeline_test.dart`') import 'dart:async'; -import 'dart:developer'; +import 'dart:developer' as dev; import 'package:grpc/grpc.dart'; import 'package:grpc/src/client/channel.dart' hide ClientChannel; import 'package:grpc/src/client/connection.dart'; import 'package:grpc/src/client/http2_connection.dart'; -import 'package:grpc/src/shared/profiler.dart'; -import 'package:mockito/mockito.dart'; import 'package:test/test.dart'; +import 'package:vm_service/vm_service.dart'; +import 'package:vm_service/vm_service_io.dart'; const String path = '/test.TestService/stream'; @@ -64,78 +66,13 @@ class FixedConnectionClientChannel extends ClientChannelBase { ClientConnection createConnection() => clientConnection; } -class FakeTimelineTask extends Fake implements TimelineTask { - static final List tasks = []; - static final List events = []; - static int _idCount = 0; - - final String? filterKey; - final TimelineTask? parent; - final int id = _idCount++; - int _startFinishCount = 0; - - factory FakeTimelineTask({TimelineTask? parent, String? filterKey}) { - final task = FakeTimelineTask._(parent: parent, filterKey: filterKey); - tasks.add(task); - return task; - } - - FakeTimelineTask._({this.parent, this.filterKey}); - - bool get isComplete => _startFinishCount == 0; - - @override - void start(String name, {Map? arguments}) { - events.add({ - 'id': id, - 'ph': 'b', - 'name': name, - 'args': { - if (filterKey != null) 'filterKey': filterKey, - if (parent != null) 'parentId': (parent as FakeTimelineTask).id, - if (arguments != null) ...arguments, - } - }); - ++_startFinishCount; - } - - @override - void instant(String name, {Map? arguments}) { - events.add({ - 'id': id, - 'ph': 'i', - 'name': name, - 'args': { - if (filterKey != null) 'filterKey': filterKey, - if (arguments != null) ...arguments, - } - }); - } - - @override - void finish({Map? arguments}) { - events.add({ - 'id': id, - 'ph': 'e', - 'args': { - if (filterKey != null) 'filterKey': filterKey, - if (arguments != null) ...arguments, - } - }); - --_startFinishCount; - expect(_startFinishCount >= 0, true); - } -} - -TimelineTask fakeTimelineTaskFactory( - {String? filterKey, TimelineTask? parent}) => - FakeTimelineTask(filterKey: filterKey, parent: parent); - -Future testee() async { +Future testee() async { + isTimelineLoggingEnabled = true; + final info = await dev.Service.getInfo(); + final uri = info.serverWebSocketUri!; + final vmService = await vmServiceConnectUri(uri.toString()); final server = Server.create(services: [TestService()]); await server.serve(address: 'localhost', port: 0); - isTimelineLoggingEnabled = true; - timelineTaskFactory = fakeTimelineTaskFactory; final channel = FixedConnectionClientChannel(Http2ClientConnection( 'localhost', server.port!, @@ -144,6 +81,7 @@ Future testee() async { final testClient = TestClient(channel); await testClient.stream(1).toList(); await server.shutdown(); + return vmService; } void checkStartEvent(List events) { @@ -151,13 +89,9 @@ void checkStartEvent(List events) { expect(e.length, 2); expect(e[0]['name'], 'gRPC Request: $path'); - expect(e[0]['id'], 0); - expect(e[0]['args']['method'], isNotNull); expect(e[0]['args']['method'], equals(path)); expect(e[1]['name'], 'gRPC Response'); - expect(e[1]['id'], 1); - expect(e[1]['args']['parentId'], 0); } void checkSendEvent(List events) { @@ -176,7 +110,6 @@ void checkReceiveEvent(List events) { expect(events.length, equals(3)); var sum = 0; for (final e in events) { - expect(e['id'], 1); // 3 elements are 1, 2 and 3. sum |= 1 << int.parse(e['args']['data']); } @@ -187,7 +120,6 @@ void checkReceiveMetaDataEvent(List events) { events = events.where((e) => e['name'] == 'Metadata received').toList(); expect(events.length, equals(2)); for (final e in events) { - expect(e['id'], 1); if (e['args']['headers'] != null) { final header = e['args']['headers']; expect(header, contains('status: 200')); @@ -205,11 +137,10 @@ void checkFinishEvent(List events) { void main([args = const []]) { test('Test gRPC timeline logging', () async { - await testee(); - for (final task in FakeTimelineTask.tasks) { - expect(task.isComplete, true); - } - final events = FakeTimelineTask.events + final vmService = await testee(); + final timeline = await vmService.getVMTimeline(); + final events = timeline.traceEvents! + .map((e) => e.json!) .where((e) => e['args']['filterKey'] == 'grpc/client') .toList(); checkStartEvent(events); @@ -218,5 +149,6 @@ void main([args = const []]) { checkReceiveEvent(events); checkReceiveMetaDataEvent(events); checkFinishEvent(events); + await vmService.dispose(); }); } diff --git a/tool/test_all.sh b/tool/test_all.sh new file mode 100755 index 0000000..160a6ff --- /dev/null +++ b/tool/test_all.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +dart test +dart run --enable-vm-service --timeline-streams=Dart test/timeline_test.dart \ No newline at end of file