Livt packages

Livt.FileSystems

Version 0.0.2

Bounded, read-only FAT32 file streaming and partition discovery for Livt FPGA applications.

Published files (15)

tests/Fat32ReaderTest.lvt

namespace Livt.FileSystems.Tests

using Livt.IO
using Livt.FileSystems

/**
 * Exercises the reader without USB, including slow consumption and bad media.
 */
@Test
component Fat32ReaderTest
{
	// Owned fixture components; each test initializes the medium before use.
	device: Fat32DiskModel
	reader: Fat32Reader<SequentialRam<byte, 512>, 4096, 2>
	name: ShortFileName

	/**
	 * Owns an isolated medium, bounded reader and filename helper.
	 */
	new()
	{
		device = new Fat32DiskModel()
		reader = new Fat32Reader<SequentialRam<byte, 512>, 4096, 2>(device)
		name = new ShortFileName()
	}

	/**
	 * Normalizes a complete alias and configures the idle reader.
	 */
	fn Select(text: byte[])
	{
		name.Reset()

		for (var index = 0; index < text.Length; index++) {
			name.Append(text[index])
		}

		assert name.IsValid()

		for (var index = 0; index < 11; index++) {
			reader.SetNameByte(index, name.GetByte(index))
		}
	}

	/**
	 * Services a bounded job, optionally checking bytes under consumer backpressure.
	 */
	fn Drain(expected: FileResult, consume: bool)
	{
		var turns = 0
		var count: uint = 0

		while (reader.result == FileResult.Active && turns < 100000) {
			reader.Service()

			if (consume && (turns % 5) == 0) {
				var value: byte

				if (reader.TryReadByte(value)) {
					var expectedByte = (count & 255) as byte

					if (count >= 1024) {
						expectedByte = ((count - 1024 + 33) & 255) as byte
					} elif (count >= 512) {
						expectedByte = ((count - 512 + 17) & 255) as byte
					}

					assert value == expectedByte
					count++
				}
			}

			turns++
		}

		assert reader.result == expected
		assert device.GetReadState() == BlockReadState.Idle

		if (consume && expected == FileResult.Success) {
			assert count == 1031
			assert reader.fileSize == 1031
			assert reader.bytesRead == 1031
		}
	}

	/**
	 * Reads the same fragmented file from superfloppy, MBR and GPT layouts.
	 */
	@Test
	fn LoadsFragmentedFileFromEachPartitionLayout()
	{
		for (var layout: byte = 0; layout < 3; layout++) {
			device.Reset(layout, 0)
			Select("data.bin".Encode())
			assert reader.TryStart(true)
			assert !reader.TryStart(false)
			Drain(FileResult.Success, true)
		}
	}

	/**
	 * Checks listing retention, empty-file completion and missing-file results.
	 */
	@Test
	fn ListsRetainedAliasesAndHandlesEmptyAndMissingFiles()
	{
		device.Reset(1, 0)
		assert reader.TryStart(false)
		var entries = 0
		var turns = 0

		while (reader.result == FileResult.Active && turns < 10000) {
			reader.Service()

			if (reader.entryReady) {
				var first = reader.GetEntryName(0)
				var size = reader.entrySize

				for (var stall = 0; stall < 10; stall++) {
					reader.Service()
					assert reader.entryReady
					assert reader.GetEntryName(0) == first
					assert reader.entrySize == size
				}

				assert reader.ConsumeEntry()
				assert !reader.ConsumeEntry()
				entries++
			}

			turns++
		}

		assert reader.result == FileResult.Success
		assert entries == 3
		Select("EMPTY.BIN".Encode())
		assert reader.TryStart(true)
		Drain(FileResult.Success, false)
		assert reader.fileSize == 0 && reader.bytesRead == 0
		Select("MISSING.BIN".Encode())
		assert reader.TryStart(true)
		Drain(FileResult.NotFound, false)
	}

	/**
	 * Rejects malformed geometry, checksums, file chains and traversal limits.
	 */
	@Test
	fn RejectsCorruptGeometryCrcAndChains()
	{
		for (var fault: byte = 1; fault <= 9; fault++) {
			var layout: byte = 0

			if (fault == 3 || fault == 4) {
				layout = 2
			}

			device.Reset(layout, fault)
			Select("DATA.BIN".Encode())
			assert reader.TryStart(true)
			var expected = FileResult.Corrupt

			if (fault >= 8) {
				expected = FileResult.LimitExceeded
			}

			Drain(expected, false)
			assert reader.bytesRead == 0
		}
	}

	/**
	 * Checks cancellation, partial-load invalidation, retry and failed byte reads.
	 */
	@Test
	fn CancelsPendingReadsAndRejectsDisconnectDuringStreaming()
	{
		device.Reset(0, 0)
		device.latency = 100
		assert reader.TryStart(false)
		reader.Cancel()
		Drain(FileResult.Cancelled, false)
		device.Reset(0, 0)
		Select("DATA.BIN".Encode())
		assert reader.TryStart(true)
		var turns = 0

		while (reader.bytesRead < 10 && turns < 10000) {
			reader.Service()
			var value: byte
			reader.TryReadByte(value)
			turns++
		}

		assert reader.bytesRead == 10
		device.attached = false
		var ignored: byte
		assert !reader.TryReadByte(ignored)
		Drain(FileResult.IoError, false)
		assert reader.bytesRead == 10
		device.Reset(0, 0)
		assert reader.TryStart(true)
		Drain(FileResult.Success, true)
		device.Reset(0, 0)
		device.rejectByte = true
		assert reader.TryStart(true)
		Drain(FileResult.IoError, false)
	}
}