blob: 18d94040d99d19e809828e8e08cb27bb613e8a78 [file] [log] [blame]
// Copyright 2019 The Fuchsia 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:typed_data';
import 'package:test/test.dart';
import 'package:fidl/fidl.dart' as fidl;
import 'package:zircon/zircon.dart';
import 'handles.dart';
// ignore: avoid_classes_with_only_static_members
abstract class Encoders {
// ignore: prefer_constructors_over_static_methods
static fidl.Encoder get v1 {
return fidl.Encoder();
}
}
// ignore: avoid_classes_with_only_static_members
abstract class Decoders {
// ignore: prefer_constructors_over_static_methods
static fidl.Decoder get v1 {
return fidl.Decoder.fromRawArgs(null, []);
}
}
/// Returns list `result` where `result[i] == data[order[i]]`. Assumes all
/// indices are valid.
List<T> _reorderList<T>(List<T> data, List<int> order) =>
order.map((index) => data[index]).toList();
fidl.OutgoingMessage _encode<T, I extends Iterable<T>>(
fidl.FidlType<T, I> type, T value) {
final fidl.Encoder encoder = fidl.Encoder()..encodeMessageHeader(0, 0);
fidl.MemberType member = fidl.MemberType(
type: type,
offset: 0,
);
fidl.encodeMessage(encoder, type.encodingInlineSize(), member, value);
return encoder.message;
}
/// Ignores the 16-byte header to return the bytes of only the message body.
Uint8List _getMessageBodyBytes(fidl.OutgoingMessage message) {
return Uint8List.view(message.data.buffer, fidl.kMessageHeaderSize,
message.data.lengthInBytes - fidl.kMessageHeaderSize);
}
T _decode<T, I extends Iterable<T>>(
fidl.FidlType<T, I> type, Uint8List bytes, List<Handle> handles) {
// The fidl.decodeMessage function assumes that the passed in FIDL message has
// a header, while the passed in byte array does not. This builder prepends
// an empty placeholder "header" to the byte array, allowing it be properly
// decoded by the function during tests.
BytesBuilder input = BytesBuilder(copy: false)
..add(Uint8List(fidl.kMessageHeaderSize))
..add(bytes);
fidl.IncomingMessage message = fidl.IncomingMessage(
ByteData.view(input.toBytes().buffer, 0, input.length), handles);
fidl.MemberType member = fidl.MemberType(
type: type,
offset: 0,
);
return fidl.decodeMessage(message, type.decodingInlineSize(), member);
}
typedef FactoryFromHandles<T> = T Function(List<Handle> handleDefs);
class EncodeSuccessCase<T, I extends Iterable<T>> {
EncodeSuccessCase(this.input, this.type, this.bytes,
{this.handles = const []});
final T input;
final fidl.FidlType<T, I> type;
final Uint8List bytes;
final List<Handle> handles;
static void run<T, I extends Iterable<T>>(fidl.Encoder encoder, String name,
T input, fidl.FidlType<T, I> type, Uint8List bytes) {
group(name, () {
EncodeSuccessCase(input, type, bytes)._checkEncode();
});
}
static void runWithHandles<T, I extends Iterable<T>>(
fidl.Encoder encoder,
String name,
FactoryFromHandles<T> inputFactory,
fidl.FidlType<T, I> type,
Uint8List bytes,
List<HandleSubtype> subtypes,
List<int> handleOrder) {
final handleDefs = createHandles(subtypes);
final expectedHandles = _reorderList(handleDefs, handleOrder);
final testCase = EncodeSuccessCase(inputFactory(handleDefs), type, bytes,
handles: expectedHandles);
group(name, () {
testCase._checkEncode();
tearDown(() {
closeHandles(testCase.handles);
});
});
}
void _checkEncode() {
test('encode', () {
final message = _encode(type, input);
expect(_getMessageBodyBytes(message), equals(bytes));
expect(message.handles, equals(handles));
});
}
}
class DecodeSuccessCase<T, I extends Iterable<T>> {
DecodeSuccessCase(this.input, this.type, this.bytes,
{this.handles = const []});
final T input;
final fidl.FidlType<T, I> type;
final Uint8List bytes;
final List<Handle> handles;
static void run<T, I extends Iterable<T>>(
String name, T input, fidl.FidlType<T, I> type, Uint8List bytes) {
group(name, () {
DecodeSuccessCase(input, type, bytes)._checkDecode();
});
}
static void runWithHandles<T, I extends Iterable<T>>(
String name,
FactoryFromHandles<T> inputFactory,
fidl.FidlType<T, I> type,
Uint8List bytes,
List<HandleSubtype> subtypes,
List<int> handleOrder,
// this parameter can be made non-optional once it is emitted in GIDL
[List<int> unusedHandles = const []]) {
final handleDefs = createHandles(subtypes);
final inputHandles = _reorderList(handleDefs, handleOrder);
final testCase = DecodeSuccessCase(inputFactory(handleDefs), type, bytes,
handles: inputHandles);
group(name, () {
testCase._checkDecode();
test('unused handles are closed', () {
expect(_reorderList(handleDefs, unusedHandles).map(isHandleClosed),
equals(unusedHandles.map((_) => true)));
}, skip: unusedHandles.isEmpty ? null : 'no unused handles');
tearDown(() {
closeHandles(Set.from(inputHandles)
.difference(Set.from(unusedHandles))
.toList()
.cast<Handle>());
});
});
}
void _checkDecode() {
test('decode', () {
expect(_decode(type, bytes, handles), equals(input));
});
}
}
typedef Factory<T> = T Function();
class EncodeFailureCase<T, I extends Iterable<T>> {
EncodeFailureCase(this.inputFactory, this.type, this.code);
final Factory<T> inputFactory;
final fidl.FidlType<T, I> type;
final fidl.FidlErrorCode code;
static void run<T, I extends Iterable<T>>(
fidl.Encoder encoder,
String name,
Factory<T> inputFactory,
fidl.FidlType<T, I> type,
fidl.FidlErrorCode code) {
group(name, () {
EncodeFailureCase(inputFactory, type, code)._checkEncodeFails();
});
}
static void runWithHandles<T, I extends Iterable<T>>(
fidl.Encoder encoder,
String name,
FactoryFromHandles<T> inputFactory,
fidl.FidlType<T, I> type,
fidl.FidlErrorCode code,
List<HandleSubtype> subtypes) {
final handleDefs = createHandles(subtypes);
group(name, () {
EncodeFailureCase(() => inputFactory(handleDefs), type, code)
._checkEncodeFails();
test('handles are closed', () {
expect(handleDefs.map(isHandleClosed),
equals(handleDefs.map((_) => true)));
});
});
}
void _checkEncodeFails() {
test('encode fails', () {
expect(() {
final input = inputFactory();
_encode(type, input);
},
throwsA(const TypeMatcher<fidl.FidlError>()
.having((e) => e.code, 'code', equals(code))));
});
}
}
class DecodeFailureCase<T, I extends Iterable<T>> {
DecodeFailureCase(this.type, this.bytes, this.code, this.handles);
final fidl.FidlType<T, I> type;
final Uint8List bytes;
final fidl.FidlErrorCode code;
final List<Handle> handles;
/// run supports DecodeFailureCases both with and without handles, depending on whether
/// the optional parameters are provided.
// The two optional parameters can be merged into a single List<HandleSubtype> in the
// desired order, but it is simpler to reorder here than having special handling
// for decode failures in the GIDL backend.
static void run<T, I extends Iterable<T>>(String name,
fidl.FidlType<T, I> type, Uint8List bytes, fidl.FidlErrorCode code,
[List<HandleSubtype> subtypes = const [],
List<int> handleOrder = const []]) {
final handles = _reorderList(createHandles(subtypes), handleOrder);
group(name, () {
DecodeFailureCase(type, bytes, code, handles)._checkDecodeFails();
test('handles are closed', () {
expect(handles.map(isHandleClosed), equals(handles.map((_) => true)));
});
});
}
void _checkDecodeFails() {
test('decode fails', () {
expect(
() => _decode(type, bytes, handles),
throwsA(const TypeMatcher<fidl.FidlError>()
.having((e) => e.code, 'code', equals(code))));
});
}
}