Livt packages

Eccelerators.Cli

Version 1.4.1

Transport-independent, bounded command-line input, parsing, editing, and output for Livt FPGA applications.

Published files (23)

tests/CommandDispatcherTest.lvt

namespace Eccelerators.Cli.Tests

using Eccelerators.Cli

/**
 * Checks constructor registration, priority, retries and caller-owned fallback
 * directly through CLI bytes, without UART timing or application command code.
 */
@Test
@Context(ClockFrequency = 100MHz)
component CommandDispatcherTest
{
	// Owned fixture; only the test process drives the shared CLI.
	cli: Cli
	first: CommandProbe
	second: CommandProbe
	fallback: CommandFallback
	firstRegistration: CommandRegistration
	secondRegistration: CommandRegistration
	dispatcher: CommandDispatcher
	emptyDispatcher: CommandDispatcher

	new()
	{
		this.cli = new Cli(echoEnabled = false)
		this.first = new CommandProbe(this.cli)
		this.second = new CommandProbe(this.cli)
		this.fallback = new CommandFallback(this.cli)
		this.secondRegistration = new CommandRegistration(this.second, this.fallback)
		this.firstRegistration = new CommandRegistration(this.first, this.secondRegistration)
		this.dispatcher = new CommandDispatcher(this.cli, this.firstRegistration)
		this.emptyDispatcher = new CommandDispatcher(this.cli, this.fallback)
	}

	fn Expect(expected: byte[])
	{
		for (var index = 0; index < expected.Length; index++) {
			assert this.cli.HasOutput()
			assert this.cli.PeekOutput() == expected[index]
			assert this.cli.ConsumeOutput()
		}

		assert !this.cli.HasOutput()
	}

	fn StartSession()
	{
		this.cli.Reset()
		this.first.attempts = 0
		this.second.attempts = 0
		this.fallback.attempts = 0
		assert this.cli.Service()
		this.Expect("> ".Encode())
	}

	fn QueueCommand(line: byte[])
	{
		for (var index = 0; index < line.Length; index++) {
			assert this.cli.AcceptByte(line[index])
		}

		assert this.cli.HasCommand()
	}

	fn FillOutput()
	{
		for (var index = 0; index < 64; index++) {
			assert this.cli.TryWriteByte(0x78)
		}
	}

	fn DrainOutput()
	{
		assert this.cli.GetOutputCount() == 64

		for (var index = 0; index < 64; index++) {
			assert this.cli.PeekOutput() == 0x78
			assert this.cli.ConsumeOutput()
		}
	}

	@Test
	fn FirstMatchCompletesOnceAndIdleDispatchDoesNothing()
	{
		this.StartSession()
		this.dispatcher.Dispatch()
		assert this.first.attempts == 0
		assert this.fallback.attempts == 0
		this.QueueCommand("ping\n".Encode())
		this.dispatcher.Dispatch()
		assert !this.cli.HasCommand()
		this.Expect("Pong\r\n".Encode())
		assert this.first.attempts == 1
		assert this.second.attempts == 0
		assert this.fallback.attempts == 0
		assert this.cli.Service()
		this.Expect("> ".Encode())
		this.dispatcher.Dispatch()
		assert this.first.attempts == 1
		assert !this.cli.HasOutput()
	}

	@Test
	fn BlockedMatchRetriesWithoutFallingThrough()
	{
		this.StartSession()
		this.QueueCommand("ping\n".Encode())
		this.FillOutput()
		this.dispatcher.Dispatch()
		this.dispatcher.Dispatch()
		assert this.cli.HasCommand()
		assert this.first.attempts == 2
		assert this.second.attempts == 0
		assert this.fallback.attempts == 0
		this.DrainOutput()
		this.dispatcher.Dispatch()
		assert !this.cli.HasCommand()
		assert this.first.attempts == 3
		this.Expect("Pong\r\n".Encode())
	}

	@Test
	fn CustomFallbackRetriesAndAlsoServesAnEmptyRegistry()
	{
		this.StartSession()
		this.QueueCommand("other\n".Encode())
		this.FillOutput()
		this.dispatcher.Dispatch()
		assert this.cli.HasCommand()
		assert this.first.attempts == 0
		assert this.second.attempts == 0
		assert this.fallback.attempts == 1
		this.DrainOutput()
		this.dispatcher.Dispatch()
		assert !this.cli.HasCommand()
		assert this.fallback.attempts == 2
		this.Expect("No handler\r\n".Encode())
		this.StartSession()
		this.QueueCommand("ping\n".Encode())
		this.emptyDispatcher.Dispatch()
		assert !this.cli.HasCommand()
		assert this.first.attempts == 0
		assert this.second.attempts == 0
		assert this.fallback.attempts == 1
		this.Expect("No handler\r\n".Encode())
	}
}