blob: c504707db5122b5201287e1a91f95e3e715ebcb8 [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.
// ignore_for_file: implementation_imports
import 'dart:typed_data';
import 'package:fuchsia_inspect/src/vmo/block.dart';
import 'package:fuchsia_inspect/src/vmo/util.dart';
import 'package:fuchsia_inspect/src/vmo/vmo_fields.dart';
import 'package:test/test.dart';
import '../util.dart';
void main() {
group('Block', () {
test('accepts state (type) correctly', () {
_accepts('lock/unlock', [BlockType.header], (block) {
block.lock();
block.unlock();
});
_accepts(
'becomeRoot', [BlockType.reserved], (block) => block.becomeRoot());
_accepts(
'becomeNode', [BlockType.anyValue], (block) => block.becomeNode());
_accepts('becomeProperty', [BlockType.anyValue],
(block) => block.becomeProperty());
_accepts('setChildren', [BlockType.nodeValue, BlockType.tombstone],
(block) => block.childCount = 0);
_accepts('getChildren', [BlockType.nodeValue, BlockType.tombstone],
(block) => block.childCount);
_accepts('setPropertyTotalLength', [BlockType.propertyValue],
(block) => block.propertyTotalLength = 0);
_accepts('getPropertyTotalLength', [BlockType.propertyValue],
(block) => block.propertyTotalLength);
_accepts('setPropertyExtentIndex', [BlockType.propertyValue],
(block) => block.propertyExtentIndex = 0);
_accepts('getPropertyExtentIndex', [BlockType.propertyValue],
(block) => block.propertyExtentIndex);
_accepts('setPropertyFlags', [BlockType.propertyValue],
(block) => block.propertyFlags = 0);
_accepts('getPropertyFlags', [BlockType.propertyValue],
(block) => block.propertyFlags);
_accepts('becomeTombstone', [BlockType.nodeValue],
(block) => block.becomeTombstone());
_accepts('becomeReserved', [BlockType.free],
(block) => block.becomeReserved());
_accepts('nextFree', [BlockType.free], (block) => block.nextFree);
_accepts('becomeValue', [BlockType.reserved],
(block) => block.becomeValue(nameIndex: 1, parentIndex: 2));
_accepts(
'nameIndex',
[
BlockType.nodeValue,
BlockType.anyValue,
BlockType.propertyValue,
BlockType.intValue,
BlockType.doubleValue
],
(block) => block.nameIndex);
_accepts(
'parentIndex',
[
BlockType.nodeValue,
BlockType.anyValue,
BlockType.propertyValue,
BlockType.intValue,
BlockType.doubleValue
],
(block) => block.parentIndex);
_accepts('becomeDoubleMetric', [BlockType.anyValue],
(block) => block.becomeDoubleMetric(0.0));
_accepts('becomeIntMetric', [BlockType.anyValue],
(block) => block.becomeIntMetric(0));
_accepts('intValueGet', [BlockType.intValue], (block) => block.intValue);
_accepts(
'intValueSet', [BlockType.intValue], (block) => block.intValue = 0);
_accepts('doubleValueGet', [BlockType.doubleValue],
(block) => block.doubleValue);
_accepts('doubleValueSet', [BlockType.doubleValue],
(block) => block.doubleValue = 0.0);
_accepts('becomeName', [BlockType.reserved],
(block) => block.becomeName('foo'));
_accepts('extentIndexSet', [BlockType.propertyValue],
(block) => block.propertyExtentIndex = 0);
_accepts(
'nextExtentGet', [BlockType.extent], (block) => block.nextExtent);
});
test('can read, including payload bits', () {
final vmo = FakeVmo(32);
vmo.bytes
..setUint8(0, 0x01 | (BlockType.propertyValue.value << 4))
..setUint8(1, 0x14) // Parent index should be 0x14
..setUint8(4, 0x20) // Name index should be 0x32. 4..7 bits of
..setUint8(5, 0x03) // byte 4 + (0..3 bits of byte 5) << 4
..setUint8(8, 0x7f) // Length should be 0x7f
..setUint8(12, 0x0a) // Extent
..setUint8(15, 0xb0); // Flags 0xb
compare(
vmo,
0,
'${hexChar(BlockType.propertyValue.value)} 1'
'14 00 00 20 03 00 00 7f00 0000 0a00 00b0');
final block = Block.read(vmo, 0);
expect(block.size, 32);
expect(block.type.value, BlockType.propertyValue.value);
expect(block.parentIndex, 0x14);
expect(block.nameIndex, 0x32);
expect(block.propertyTotalLength, 0x7f);
expect(block.propertyExtentIndex, 0xa);
expect(block.propertyFlags, 0xb);
});
test('can read, including payload bytes', () {
final vmo = FakeVmo(32);
vmo.bytes
..setUint8(0, 0x01 | (BlockType.nameUtf8.value << 4))
..setUint8(1, 0x02) // Set length to 2
..setUint8(8, 0x41) // 'a'
..setUint8(9, 0x42); // 'b'
compare(
vmo,
0,
'${hexChar(BlockType.nameUtf8.value)} 1'
'02 00 00 0000 0000 4142 0000 0000 0000 0000');
final block = Block.read(vmo, 0);
expect(block.size, 32);
expect(block.type.value, BlockType.nameUtf8.value);
expect(block.payloadBytes.getUint8(0), 0x41);
expect(block.payloadBytes.getUint8(1), 0x42);
});
});
group('Block operations write to VMO correctly:', () {
test('Creating, locking, and unlocking the VMO header', () {
final vmo = FakeVmo(32);
final block = Block.create(vmo, 0)..becomeHeader();
compare(
vmo,
0,
'${hexChar(BlockType.header.value)} 0'
'00 0000 49 4E 53 50 0000 0000 0000 0000');
block.lock();
compare(
vmo,
0,
'${hexChar(BlockType.header.value)} 0'
'00 0000 49 4E 53 50 0100 0000 0000 0000');
block.unlock();
compare(
vmo,
0,
'${hexChar(BlockType.header.value)} 0'
'00 0000 49 4E 53 50 0200 0000 0000 0000');
});
test('Becoming the special root node', () {
final vmo = FakeVmo(64);
Block.create(vmo, 1).becomeRoot();
compare(vmo, 16,
'${hexChar(BlockType.nodeValue.value)} 0 00 0000 2000 0000 0000');
});
test('Becoming and modifying an intValue via free, reserved, anyValue', () {
final vmo = FakeVmo(64);
final block = Block.create(vmo, 2)..becomeFree(5);
compare(vmo, 32, '${hexChar(BlockType.free.value)} 1 05 00 00 0000 0000');
expect(block.nextFree, 5);
block.becomeReserved();
compare(vmo, 32, '${hexChar(BlockType.reserved.value)} 1');
block.becomeValue(parentIndex: 0xbc, nameIndex: 0x7d);
compare(vmo, 32,
'${hexChar(BlockType.anyValue.value)} 1 bc 00 00 d0 07 00 00');
block.becomeIntMetric(0xbeef);
compare(vmo, 32,
'${hexChar(BlockType.intValue.value)} 1 bc 00 00 d0 07 00 00 efbe');
block.intValue += 1;
compare(vmo, 32,
'${hexChar(BlockType.intValue.value)} 1 bc 00 00 d0 07 00 00 f0be');
});
test('Becoming a nodeValue and then a tombstone', () {
final vmo = FakeVmo(64);
final block = Block.create(vmo, 2)
..becomeFree(5)
..becomeReserved()
..becomeValue(parentIndex: 0xbc, nameIndex: 0x7d)
..becomeNode();
compare(vmo, 32,
'${hexChar(BlockType.nodeValue.value)} 1 bc 00 00 d0 07 00 00 0000');
block.childCount += 1;
compare(vmo, 32,
'${hexChar(BlockType.nodeValue.value)} 1 bc 00 00 d0 07 00 00 0100');
block.becomeTombstone();
compare(vmo, 32,
'${hexChar(BlockType.tombstone.value)} 1 bc 00 00 d0 07 00 00 0100');
});
test('Becoming and modifying doubleValue', () {
final vmo = FakeVmo(64);
final block = Block.create(vmo, 2)
..becomeFree(5)
..becomeReserved()
..becomeValue(parentIndex: 0xbc, nameIndex: 0x7d)
..becomeDoubleMetric(1.0);
compare(vmo, 32,
'${hexChar(BlockType.doubleValue.value)} 1 bc 00 00 d0 07 00 00 ');
expect(vmo.bytes.getFloat64(40, Endian.little), 1.0);
block.doubleValue++;
expect(vmo.bytes.getFloat64(40, Endian.little), 2.0);
});
test('Becoming and modifying a propertyValue', () {
final vmo = FakeVmo(64);
final block = Block.create(vmo, 2)
..becomeFree(5)
..becomeReserved()
..becomeValue(parentIndex: 0xbc, nameIndex: 0x7d)
..becomeProperty();
compare(
vmo,
32,
'${hexChar(BlockType.propertyValue.value)} 1 bc 00 00 d0 07 00 00 '
'00 00 00 00 00 00 00 00');
block
..propertyExtentIndex = 0x35
..propertyTotalLength = 0x17b
..propertyFlags = 0xa;
compare(
vmo,
32,
'${hexChar(BlockType.propertyValue.value)} 1 bc 00 00 d0 07 00 00 '
'7b 01 00 00 35 00 00 a0');
expect(block.propertyTotalLength, 0x17b);
expect(block.propertyExtentIndex, 0x35);
expect(block.propertyFlags, 0xa);
});
test('Becoming a name', () {
final vmo = FakeVmo(64);
final block = Block.create(vmo, 2)..becomeName('abc');
compare(vmo, 32,
'${hexChar(BlockType.nameUtf8.value)} 1 03 0000 0000 0000 61 62 63');
expect(
Uint8List.view(
block.nameUtf8.buffer, 0, block.nameUtf8.lengthInBytes),
Uint8List.fromList([0x61, 0x62, 0x63]));
});
test('Becoming and setting an extent', () {
final vmo = FakeVmo(64);
final block = Block.create(vmo, 2)
..becomeFree(4)
..becomeReserved()
..becomeExtent(0x42)
..setExtentPayload(toByteData('abc'));
compare(vmo, 32,
'${hexChar(BlockType.extent.value)} 1 42 0000 0000 0000 61 62 63');
expect(block.nextExtent, 0x42);
expect(block.payloadSpaceBytes, block.size - headerSizeBytes);
});
});
}
/// Verify which block types are accepted by which functions.
///
/// For all block types (including anyValue), creates a block of that type and
/// passes it to [testFunction].
/// [previousStates] contains the types that should not throw
/// an error. All others should throw.
void _accepts(String testName, List<BlockType> previousStates, testFunction) {
final vmo = FakeVmo(4096);
for (BlockType type in BlockType.values) {
final block = Block.createWithType(vmo, 0, type);
if (previousStates.contains(type)) {
expect(() => testFunction(block), returnsNormally,
reason: '$testName should have accepted type $type');
} else {
expect(() => testFunction(block), throwsA(anything),
reason: '$testName should not accept type $type');
}
}
}