blob: 9a98af39501a906caeb7f0932a61609e429b28ad [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:io';
import 'dart:math' show min;
import 'dart:typed_data';
import 'package:collection/collection.dart';
import 'package:fuchsia_inspect/src/block.dart';
import 'package:fuchsia_inspect/src/vmo_holder.dart';
import 'package:test/test.dart';
import 'package:zircon/zircon.dart';
class FakeVmo implements VmoHolder {
/// The memory contents of this "VMO".
final ByteData bytes;
@override
Vmo get vmo => null;
/// Size of the "VMO".
@override
final int size;
/// Creates non-shared (ByteData) memory to simulate VMO operations.
FakeVmo(this.size) : bytes = ByteData(size);
@override
void beginWork() {}
@override
void commit() {}
/// Writes to the "VMO".
@override
void write(int offset, ByteData data) {
bytes.buffer.asUint8List().setAll(offset,
data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes));
}
/// Reads from the "VMO".
@override
ByteData read(int offset, int size) {
var reading = ByteData(size);
reading.buffer
.asUint8List()
.setAll(0, bytes.buffer.asUint8List(offset, size));
return reading;
}
/// Writes int64 to VMO.
@override
void writeInt64(int offset, int value) =>
bytes.setInt64(offset, value, Endian.little);
/// Writes int64 directly to VMO for immediate visibility.
@override
void writeInt64Direct(int offset, int value) => writeInt64(offset, value);
/// Reads int64 from VMO.
@override
int readInt64(int offset) => bytes.getInt64(offset, Endian.little);
}
/// Returns the ascii code of this character.
int ascii(String char) {
if (char.length != 1) {
throw ArgumentError('char must be 1 character long.');
}
var code = char.codeUnitAt(0);
if (code > 127) {
throw ArgumentError("char wasn't ascii (code $code)");
}
return code;
}
/// returns the hex char corresponding to a 0..15 value.
String hexChar(int value) {
if (value < 0 || value > 15) {
throw ArgumentError('Bad value $value');
}
return value.toRadixString(16);
}
/// Compares contents, starting at [offset], with the hex values in [spec].
///
/// Valid chars in [spec] are:
/// ' ' (ignored completely)
/// _ x X (skips 4 bits)
/// 0..9 a..f A..F (hex value of 4 bits)
///
/// [spec] is little-endian, which makes integer values look weird. If you
/// write 0x234 into memory, it'll be matched by '34 02' (or by 'x4_2')
void compare(FakeVmo vmo, int offset, String spec) {
int nybble = offset * 2;
for (int i = 0; i < spec.length; i++) {
int rune = spec.codeUnitAt(i);
if (rune == ascii(' ')) {
continue;
}
if (rune == ascii('_') || rune == ascii('x') || rune == ascii('X')) {
nybble++;
continue;
}
int value;
if (rune >= ascii('0') && rune <= ascii('9')) {
value = rune - ascii('0');
} else if (rune >= ascii('a') && rune <= ascii('f')) {
value = rune - ascii('a') + 10;
} else if (rune >= ascii('A') && rune <= ascii('F')) {
value = rune - ascii('A') + 10;
} else {
throw ArgumentError('Illegal char "${String.fromCharCode(rune)}"');
}
int byte = nybble ~/ 2;
int dataAtByte = vmo.bytes.getUint8(byte);
int dataAtNybble = (nybble & 1 == 0) ? dataAtByte >> 4 : dataAtByte & 0xf;
if (dataAtNybble != value) {
expect(dataAtNybble, value,
reason: 'byte[$byte] = ${dataAtByte.toRadixString(16)}. '
'Nybble $nybble was ${dataAtNybble.toRadixString(16)} '
'but expected ${value.toRadixString(16)}.');
}
nybble++;
}
}
/// Writes block contents in hexadecimal, nicely formatted, to stdout.
///
/// This is very useful in debugging, so I'll leave it in although it's not
/// used in testing.
void dumpBlocks(FakeVmo vmo, {int startIndex = 0, int howMany32 = -1}) {
int lastIndex = (howMany32 == -1)
? (vmo.bytes.lengthInBytes >> 4) - 1
: startIndex + howMany32 - 1;
stdout.writeln('Dumping blocks from $startIndex through $lastIndex');
for (int index = startIndex; index <= lastIndex;) {
String lowNybble(int offset) => hexChar(vmo.bytes.getUint8(offset) & 15);
String highNybble(int offset) => hexChar(vmo.bytes.getUint8(offset) >> 4);
stdout.write('${(index * 16).toRadixString(16).padLeft(3, '0')}: ');
for (int byte = 0; byte < 8; byte++) {
stdout
..write('${lowNybble(index * 16 + byte)} ')
..write('${highNybble(index * 16 + byte)} ');
}
int order = vmo.bytes.getUint8(index * 16) & 0xf;
int numWords = 1 << (order + 1);
String byteToHex(int offset) =>
vmo.bytes.getUint8(offset).toRadixString(16).padLeft(2, '0');
for (int word = 1; word < numWords; word++) {
stdout.write(' ');
for (int byte = 0; byte < 8; byte++) {
stdout.write('${byteToHex(index * 16 + word * 8 + byte)} ');
}
}
index += 1 << order;
stdout.writeln('');
}
}
/// Reads the property at [propertyIndex] out of [vmo] and returns the value.
ByteData readProperty(FakeVmo vmo, int propertyIndex) {
final property = Block.read(vmo, propertyIndex);
final totalLength = property.propertyTotalLength;
final data = ByteData(totalLength);
if (totalLength == 0) {
return data;
}
var nextExtentIndex = property.propertyExtentIndex;
int offset = 0;
while (offset < totalLength) {
final extent = Block.read(vmo, nextExtentIndex);
int amountToCopy = min(totalLength - offset, extent.payloadSpaceBytes);
data.buffer.asUint8List().setRange(offset, offset + amountToCopy,
extent.payloadBytes.buffer.asUint8List());
offset += amountToCopy;
nextExtentIndex = extent.nextExtent;
}
return data;
}
/// A matcher that matches ByteData values as unit8 lists.
Matcher equalsByteData(ByteData data) => _EqualsByteData(data);
class _EqualsByteData extends Matcher {
final ByteData _other;
const _EqualsByteData(this._other);
@override
bool matches(dynamic item, _) {
if (item is! ByteData) {
return false;
}
var listEquals = const ListEquality().equals;
ByteData byteData = item;
return listEquals(
byteData.buffer.asUint8List(), _other.buffer.asUint8List());
}
@override
Description describe(Description description) =>
description.add('buffer as uint8 list: ${_other.buffer.asUint8List()}');
@override
Description describeMismatch(
dynamic item, Description mismatchDescription, _, __) {
if (item is! ByteData) {
return mismatchDescription.add('$item is not of type ByteData');
}
ByteData byteData = item;
return mismatchDescription
.replace('buffer as uint8 list: ${byteData.buffer.asUint8List()}');
}
}