1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-08-27 07:52:18 +00:00

remove partial java and lua test suites

This commit is contained in:
Jummit 2021-05-25 18:08:15 +02:00
parent 63aa3d8877
commit bff2e81713
47 changed files with 0 additions and 6648 deletions

View File

@ -52,10 +52,6 @@ dependencies {
shade 'org.squiddev:Cobalt:0.5.1-SNAPSHOT'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0'
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.7.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0'
modRuntime "me.shedaniel:RoughlyEnoughItems-api:5.8.9"
modRuntime "me.shedaniel:RoughlyEnoughItems:5.8.9"
}

View File

@ -1,59 +0,0 @@
package badhtmlparser;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.util.Iterator;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Iterators;
import com.google.common.collect.Multimap;
import com.google.common.collect.PeekingIterator;
public class BadHtmlParser {
private static final Multimap<String, String> IDS = HashMultimap.create();
public static void main(String[] args) throws FileNotFoundException {
BufferedReader reader = new BufferedReader(new InputStreamReader(BadHtmlParser.class.getResourceAsStream("/tags.html")));
PeekingIterator<String> iterator = Iterators.peekingIterator(reader.lines()
.iterator());
while (iterator.hasNext()) {
String str = iterator.peek();
try {
int count = Integer.parseInt(str);
iterator.next();
write(iterator, count);
} catch (NumberFormatException e) {
write(iterator, 1);
}
}
System.out.println(IDS);
print("iron_ingots");
// todo replace dyes by hand
}
private static void print(String val) throws FileNotFoundException {
FileOutputStream fos = new FileOutputStream("src/main/resources/data/c/tags/items/" + val + ".json");
PrintStream stream = new PrintStream(fos);
stream.println("{\"replace\": false,\"values\":[");
Iterator<String> iterator = IDS.get("c:" + val)
.iterator();
while (iterator.hasNext()) {
String s = iterator.next();
stream.printf("\"%s\"%s", s, iterator.hasNext() ? ',' : "");
}
stream.println("]}");
}
private static void write(Iterator<String> str, int entries) {
String tag = str.next();
for (int i = 0; i < entries; i++) {
IDS.put(tag, str.next());
}
}
}

View File

@ -1,489 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.filesystem.IWritableMount;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.core.computer.BasicEnvironment;
import dan200.computercraft.core.computer.Computer;
import dan200.computercraft.core.computer.ComputerSide;
import dan200.computercraft.core.computer.MainThread;
import dan200.computercraft.core.filesystem.FileMount;
import dan200.computercraft.core.filesystem.FileSystemException;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.shared.peripheral.modem.ModemState;
import dan200.computercraft.shared.peripheral.modem.wireless.WirelessModemPeripheral;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.function.Executable;
import org.opentest4j.AssertionFailedError;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.Writer;
import java.nio.channels.Channels;
import java.nio.channels.WritableByteChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Stream;
import static dan200.computercraft.api.lua.LuaValues.getType;
/**
* Loads tests from {@code test-rom/spec} and executes them.
*
* This spins up a new computer and runs the {@code mcfly.lua} script. This will then load all files in the {@code spec}
* directory and register them with {@code cct_test.start}.
*
* From the test names, we generate a tree of {@link DynamicNode}s which queue an event and wait for
* {@code cct_test.submit} to be called. McFly pulls these events, executes the tests and then calls the submit method.
*
* Once all tests are done, we invoke {@code cct_test.finish} in order to mark everything as complete.
*/
public class ComputerTestDelegate
{
private static final File REPORT_PATH = new File( "test-files/luacov.report.out" );
private static final Logger LOG = LogManager.getLogger( ComputerTestDelegate.class );
private static final long TICK_TIME = TimeUnit.MILLISECONDS.toNanos( 50 );
private static final long TIMEOUT = TimeUnit.SECONDS.toNanos( 10 );
private final ReentrantLock lock = new ReentrantLock();
private Computer computer;
private final Condition hasTests = lock.newCondition();
private DynamicNodeBuilder tests;
private final Condition hasRun = lock.newCondition();
private String currentTest;
private boolean runFinished;
private Throwable runResult;
private final Condition hasFinished = lock.newCondition();
private boolean finished = false;
private Map<String, Map<Double, Double>> finishedWith;
@BeforeEach
public void before() throws IOException
{
ComputerCraft.logPeripheralErrors = true;
if( REPORT_PATH.delete() ) ComputerCraft.log.info( "Deleted previous coverage report." );
Terminal term = new Terminal( 78, 20 );
IWritableMount mount = new FileMount( new File( "test-files/mount" ), 10_000_000 );
// Remove any existing files
List<String> children = new ArrayList<>();
mount.list( "", children );
for( String child : children ) mount.delete( child );
// And add our startup file
try( WritableByteChannel channel = mount.openForWrite( "startup.lua" );
Writer writer = Channels.newWriter( channel, StandardCharsets.UTF_8.newEncoder(), -1 ) )
{
writer.write( "loadfile('test-rom/mcfly.lua', nil, _ENV)('test-rom/spec') cct_test.finish()" );
}
computer = new Computer( new BasicEnvironment( mount ), term, 0 );
computer.getEnvironment().setPeripheral( ComputerSide.TOP, new FakeModem() );
computer.addApi( new CctTestAPI() );
computer.turnOn();
}
@AfterEach
public void after() throws InterruptedException, IOException
{
try
{
LOG.info( "Finished execution" );
computer.queueEvent( "cct_test_run", null );
// Wait for test execution to fully finish
lock.lockInterruptibly();
try
{
long remaining = TIMEOUT;
while( remaining > 0 && !finished )
{
tick();
if( hasFinished.awaitNanos( TICK_TIME ) > 0 ) break;
remaining -= TICK_TIME;
}
if( remaining <= 0 ) throw new IllegalStateException( "Timed out waiting for finish." + dump() );
if( !finished ) throw new IllegalStateException( "Computer did not finish." + dump() );
}
finally
{
lock.unlock();
}
}
finally
{
// Show a dump of computer output
System.out.println( dump() );
// And shutdown
computer.shutdown();
}
if( finishedWith != null )
{
REPORT_PATH.getParentFile().mkdirs();
try( BufferedWriter writer = Files.newBufferedWriter( REPORT_PATH.toPath() ) )
{
// new LuaCoverage( finishedWith ).write( writer );
}
}
}
@TestFactory
public Stream<DynamicNode> get() throws InterruptedException
{
lock.lockInterruptibly();
try
{
long remaining = TIMEOUT;
while( remaining > 0 & tests == null )
{
tick();
if( hasTests.awaitNanos( TICK_TIME ) > 0 ) break;
remaining -= TICK_TIME;
}
if( remaining <= 0 ) throw new IllegalStateException( "Timed out waiting for tests. " + dump() );
if( tests == null ) throw new IllegalStateException( "Computer did not provide any tests. " + dump() );
}
finally
{
lock.unlock();
}
return tests.buildChildren();
}
private static class DynamicNodeBuilder
{
private final String name;
private final Map<String, DynamicNodeBuilder> children;
private final Executable executor;
DynamicNodeBuilder( String name )
{
this.name = name;
this.children = new HashMap<>();
this.executor = null;
}
DynamicNodeBuilder( String name, Executable executor )
{
this.name = name;
this.children = Collections.emptyMap();
this.executor = executor;
}
DynamicNodeBuilder get( String name )
{
DynamicNodeBuilder child = children.get( name );
if( child == null ) children.put( name, child = new DynamicNodeBuilder( name ) );
return child;
}
void runs( String name, Executable executor )
{
DynamicNodeBuilder child = children.get( name );
int id = 0;
while( child != null )
{
id++;
String subName = name + "_" + id;
child = children.get( subName );
}
children.put( name, new DynamicNodeBuilder( name, executor ) );
}
DynamicNode build()
{
return executor == null
? DynamicContainer.dynamicContainer( name, buildChildren() )
: DynamicTest.dynamicTest( name, executor );
}
Stream<DynamicNode> buildChildren()
{
return children.values().stream().map( DynamicNodeBuilder::build );
}
}
private String dump()
{
if( !computer.isOn() ) return "Computer is currently off.";
Terminal term = computer.getAPIEnvironment().getTerminal();
StringBuilder builder = new StringBuilder().append( "Computer is currently on.\n" );
for( int line = 0; line < term.getHeight(); line++ )
{
builder.append( String.format( "%2d | %" + term.getWidth() + "s |\n", line + 1, term.getLine( line ) ) );
}
computer.shutdown();
return builder.toString();
}
private void tick()
{
computer.tick();
MainThread.executePendingTasks();
}
private static String formatName( String name )
{
return name.replace( "\0", " -> " );
}
private static class FakeModem extends WirelessModemPeripheral
{
FakeModem()
{
super( new ModemState(), true );
}
@Nonnull
@Override
@SuppressWarnings( "ConstantConditions" )
public World getWorld()
{
return null;
}
@Nonnull
@Override
public Vec3d getPosition()
{
return Vec3d.ZERO;
}
@Override
public boolean equals( @Nullable IPeripheral other )
{
return this == other;
}
}
public class CctTestAPI implements ILuaAPI
{
@Override
public String[] getNames()
{
return new String[] { "cct_test" };
}
@Override
public void startup()
{
try
{
computer.getAPIEnvironment().getFileSystem().mount(
"test-rom", "test-rom",
BasicEnvironment.createMount( ComputerTestDelegate.class, "test-rom", "test" )
);
}
catch( FileSystemException e )
{
throw new IllegalStateException( e );
}
}
@LuaFunction
public final void start( Map<?, ?> tests ) throws LuaException
{
// Submit several tests and signal for #get to run
LOG.info( "Received tests from computer" );
DynamicNodeBuilder root = new DynamicNodeBuilder( "" );
for( Object key : tests.keySet() )
{
if( !(key instanceof String) ) throw new LuaException( "Non-key string " + getType( key ) );
String name = (String) key;
String[] parts = name.split( "\0" );
DynamicNodeBuilder builder = root;
for( int i = 0; i < parts.length - 1; i++ ) builder = builder.get( parts[i] );
builder.runs( parts[parts.length - 1], () -> {
// Run it
lock.lockInterruptibly();
try
{
// Set the current test
runResult = null;
runFinished = false;
currentTest = name;
// Tell the computer to run it
LOG.info( "Starting '{}'", formatName( name ) );
computer.queueEvent( "cct_test_run", new Object[] { name } );
long remaining = TIMEOUT;
while( remaining > 0 && computer.isOn() && !runFinished )
{
tick();
long waiting = hasRun.awaitNanos( TICK_TIME );
if( waiting > 0 ) break;
remaining -= TICK_TIME;
}
LOG.info( "Finished '{}'", formatName( name ) );
if( remaining <= 0 )
{
throw new IllegalStateException( "Timed out waiting for test" );
}
else if( !computer.isOn() )
{
throw new IllegalStateException( "Computer turned off mid-execution" );
}
if( runResult != null ) throw runResult;
}
finally
{
lock.unlock();
currentTest = null;
}
} );
}
try
{
lock.lockInterruptibly();
}
catch( InterruptedException e )
{
throw new RuntimeException( e );
}
try
{
ComputerTestDelegate.this.tests = root;
hasTests.signal();
}
finally
{
lock.unlock();
}
}
@LuaFunction
public final void submit( Map<?, ?> tbl )
{
// Submit the result of a test, allowing the test executor to continue
String name = (String) tbl.get( "name" );
if( name == null )
{
ComputerCraft.log.error( "Oh no: {}", tbl );
}
String status = (String) tbl.get( "status" );
String message = (String) tbl.get( "message" );
String trace = (String) tbl.get( "trace" );
StringBuilder wholeMessage = new StringBuilder();
if( message != null ) wholeMessage.append( message );
if( trace != null )
{
if( wholeMessage.length() != 0 ) wholeMessage.append( '\n' );
wholeMessage.append( trace );
}
try
{
lock.lockInterruptibly();
}
catch( InterruptedException e )
{
throw new RuntimeException( e );
}
try
{
LOG.info( "'{}' finished with {}", formatName( name ), status );
// Skip if a test mismatch
if( !name.equals( currentTest ) )
{
LOG.warn( "Skipping test '{}', as we're currently executing '{}'", formatName( name ), formatName( currentTest ) );
return;
}
switch( status )
{
case "ok":
case "pending":
break;
case "fail":
runResult = new AssertionFailedError( wholeMessage.toString() );
break;
case "error":
runResult = new IllegalStateException( wholeMessage.toString() );
break;
}
runFinished = true;
hasRun.signal();
}
finally
{
lock.unlock();
}
}
@LuaFunction
public final void finish( Optional<Map<?, ?>> result )
{
@SuppressWarnings( "unchecked" )
Map<String, Map<Double, Double>> finishedResult = (Map<String, Map<Double, Double>>) result.orElse( null );
LOG.info( "Finished" );
// Signal to after that execution has finished
try
{
lock.lockInterruptibly();
}
catch( InterruptedException e )
{
throw new RuntimeException( e );
}
try
{
finished = true;
if( finishedResult != null ) finishedWith = finishedResult;
hasFinished.signal();
}
finally
{
lock.unlock();
}
}
}
}

View File

@ -1,67 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.apis;
import dan200.computercraft.api.lua.*;
import dan200.computercraft.core.asm.LuaMethod;
import dan200.computercraft.core.asm.NamedMethod;
import javax.annotation.Nonnull;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
public class ObjectWrapper implements ILuaContext
{
private final Object object;
private final Map<String, LuaMethod> methodMap;
public ObjectWrapper( Object object )
{
this.object = object;
String[] dynamicMethods = object instanceof IDynamicLuaObject
? Objects.requireNonNull( ((IDynamicLuaObject) object).getMethodNames(), "Methods cannot be null" )
: LuaMethod.EMPTY_METHODS;
List<NamedMethod<LuaMethod>> methods = LuaMethod.GENERATOR.getMethods( object.getClass() );
Map<String, LuaMethod> methodMap = this.methodMap = new HashMap<>( methods.size() + dynamicMethods.length );
for( int i = 0; i < dynamicMethods.length; i++ )
{
methodMap.put( dynamicMethods[i], LuaMethod.DYNAMIC.get( i ) );
}
for( NamedMethod<LuaMethod> method : methods )
{
methodMap.put( method.getName(), method.getMethod() );
}
}
public Object[] call( String name, Object... args ) throws LuaException
{
LuaMethod method = methodMap.get( name );
if( method == null ) throw new IllegalStateException( "No such method '" + name + "'" );
return method.apply( object, this, new ObjectArguments( args ) ).getResult();
}
@SuppressWarnings( "unchecked" )
public <T> T callOf( String name, Object... args ) throws LuaException
{
return (T) call( name, args )[0];
}
public <T> T callOf( Class<T> klass, String name, Object... args ) throws LuaException
{
return klass.cast( call( name, args )[0] );
}
@Override
public long issueMainThreadTask( @Nonnull ILuaTask task )
{
throw new IllegalStateException( "Method should never queue events" );
}
}

View File

@ -1,86 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.apis.handles;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.core.apis.ObjectWrapper;
import org.junit.jupiter.api.Test;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import static org.junit.jupiter.api.Assertions.*;
public class BinaryReadableHandleTest
{
@Test
public void testReadChar() throws LuaException
{
ObjectWrapper wrapper = fromLength( 5 );
assertEquals( 'A', (int) wrapper.callOf( Integer.class, "read" ) );
}
@Test
public void testReadShortComplete() throws LuaException
{
ObjectWrapper wrapper = fromLength( 10 );
assertEquals( 5, wrapper.<ByteBuffer>callOf( "read", 5 ).remaining() );
}
@Test
public void testReadShortPartial() throws LuaException
{
ObjectWrapper wrapper = fromLength( 5 );
assertEquals( 5, wrapper.<ByteBuffer>callOf( "read", 10 ).remaining() );
}
@Test
public void testReadLongComplete() throws LuaException
{
ObjectWrapper wrapper = fromLength( 10000 );
assertEquals( 9000, wrapper.<byte[]>callOf( "read", 9000 ).length );
}
@Test
public void testReadLongPartial() throws LuaException
{
ObjectWrapper wrapper = fromLength( 10000 );
assertEquals( 10000, wrapper.<byte[]>callOf( "read", 11000 ).length );
}
@Test
public void testReadLongPartialSmaller() throws LuaException
{
ObjectWrapper wrapper = fromLength( 1000 );
assertEquals( 1000, wrapper.<ByteBuffer>callOf( "read", 11000 ).remaining() );
}
@Test
public void testReadLine() throws LuaException
{
ObjectWrapper wrapper = new ObjectWrapper( BinaryReadableHandle.of( new ArrayByteChannel( "hello\r\nworld\r!".getBytes( StandardCharsets.UTF_8 ) ) ) );
assertArrayEquals( "hello".getBytes( StandardCharsets.UTF_8 ), wrapper.callOf( "readLine" ) );
assertArrayEquals( "world\r!".getBytes( StandardCharsets.UTF_8 ), wrapper.callOf( "readLine" ) );
assertNull( wrapper.call( "readLine" ) );
}
@Test
public void testReadLineTrailing() throws LuaException
{
ObjectWrapper wrapper = new ObjectWrapper( BinaryReadableHandle.of( new ArrayByteChannel( "hello\r\nworld\r!".getBytes( StandardCharsets.UTF_8 ) ) ) );
assertArrayEquals( "hello\r\n".getBytes( StandardCharsets.UTF_8 ), wrapper.callOf( "readLine", true ) );
assertArrayEquals( "world\r!".getBytes( StandardCharsets.UTF_8 ), wrapper.callOf( "readLine", true ) );
assertNull( wrapper.call( "readLine", true ) );
}
private static ObjectWrapper fromLength( int length )
{
byte[] input = new byte[length];
Arrays.fill( input, (byte) 'A' );
return new ObjectWrapper( BinaryReadableHandle.of( new ArrayByteChannel( input ) ) );
}
}

View File

@ -1,69 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.apis.handles;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.core.apis.ObjectWrapper;
import org.junit.jupiter.api.Test;
import java.io.BufferedReader;
import java.io.CharArrayReader;
import java.util.Arrays;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class EncodedReadableHandleTest
{
@Test
public void testReadChar() throws LuaException
{
ObjectWrapper wrapper = fromLength( 5 );
assertEquals( "A", wrapper.callOf( "read" ) );
}
@Test
public void testReadShortComplete() throws LuaException
{
ObjectWrapper wrapper = fromLength( 10 );
assertEquals( "AAAAA", wrapper.callOf( "read", 5 ) );
}
@Test
public void testReadShortPartial() throws LuaException
{
ObjectWrapper wrapper = fromLength( 5 );
assertEquals( "AAAAA", wrapper.callOf( "read", 10 ) );
}
@Test
public void testReadLongComplete() throws LuaException
{
ObjectWrapper wrapper = fromLength( 10000 );
assertEquals( 9000, wrapper.<String>callOf( "read", 9000 ).length() );
}
@Test
public void testReadLongPartial() throws LuaException
{
ObjectWrapper wrapper = fromLength( 10000 );
assertEquals( 10000, wrapper.<String>callOf( "read", 11000 ).length() );
}
@Test
public void testReadLongPartialSmaller() throws LuaException
{
ObjectWrapper wrapper = fromLength( 1000 );
assertEquals( 1000, wrapper.<String>callOf( "read", 11000 ).length() );
}
private static ObjectWrapper fromLength( int length )
{
char[] input = new char[length];
Arrays.fill( input, 'A' );
return new ObjectWrapper( new EncodedReadableHandle( new BufferedReader( new CharArrayReader( input ) ) ) );
}
}

View File

@ -1,33 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.apis.http.options;
import org.junit.jupiter.api.Test;
import java.net.InetSocketAddress;
import java.util.Collections;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class AddressRuleTest
{
@Test
public void matchesPort()
{
Iterable<AddressRule> rules = Collections.singletonList( AddressRule.parse(
"127.0.0.1", 8080,
new PartialOptions( Action.ALLOW, null, null, null, null )
) );
assertEquals( apply( rules, "localhost", 8080 ).action, Action.ALLOW );
assertEquals( apply( rules, "localhost", 8081 ).action, Action.DENY );
}
private Options apply( Iterable<AddressRule> rules, String host, int port )
{
return AddressRule.apply( rules, host, new InetSocketAddress( host, port ) );
}
}

View File

@ -1,165 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.computer;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.api.filesystem.IWritableMount;
import dan200.computercraft.core.filesystem.FileMount;
import dan200.computercraft.core.filesystem.JarMount;
import dan200.computercraft.core.filesystem.MemoryMount;
import javax.annotation.Nonnull;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
/**
* A very basic environment.
*/
public class BasicEnvironment implements IComputerEnvironment
{
private final IWritableMount mount;
public BasicEnvironment()
{
this( new MemoryMount() );
}
public BasicEnvironment( IWritableMount mount )
{
this.mount = mount;
}
@Override
public int assignNewID()
{
return 0;
}
@Override
public IWritableMount createSaveDirMount( String path, long space )
{
return mount;
}
@Override
public int getDay()
{
return 0;
}
@Override
public double getTimeOfDay()
{
return 0;
}
@Override
public boolean isColour()
{
return true;
}
@Override
public long getComputerSpaceLimit()
{
return ComputerCraft.computerSpaceLimit;
}
@Nonnull
@Override
public String getHostString()
{
return "ComputerCraft 1.0 (Test environment)";
}
@Nonnull
@Override
public String getUserAgent()
{
return "ComputerCraft/1.0";
}
@Override
public IMount createResourceMount( String domain, String subPath )
{
return createMount( ComputerCraft.class, "data/" + domain + "/" + subPath, "main" );
}
@Override
public InputStream createResourceFile( String domain, String subPath )
{
return ComputerCraft.class.getClassLoader().getResourceAsStream( "data/" + domain + "/" + subPath );
}
public static IMount createMount( Class<?> klass, String path, String fallback )
{
File file = getContainingFile( klass );
if( file.isFile() )
{
try
{
return new JarMount( file, path );
}
catch( IOException e )
{
throw new UncheckedIOException( e );
}
}
else
{
File wholeFile = new File( file, path );
// If we don't exist, walk up the tree looking for resource folders
File baseFile = file;
while( baseFile != null && !wholeFile.exists() )
{
baseFile = baseFile.getParentFile();
wholeFile = new File( baseFile, "src/" + fallback + "/resources/" + path );
}
if( !wholeFile.exists() ) throw new IllegalStateException( "Cannot find ROM mount at " + file );
return new FileMount( wholeFile, 0 );
}
}
private static File getContainingFile( Class<?> klass )
{
String path = klass.getProtectionDomain().getCodeSource().getLocation().getPath();
int bangIndex = path.indexOf( "!" );
// Plain old file, so step up from dan200.computercraft.
if( bangIndex < 0 ) return new File( path );
path = path.substring( 0, bangIndex );
URL url;
try
{
url = new URL( path );
}
catch( MalformedURLException e )
{
throw new IllegalStateException( e );
}
try
{
return new File( url.toURI() );
}
catch( URISyntaxException e )
{
return new File( url.getPath() );
}
}
}

View File

@ -1,138 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.computer;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.filesystem.IWritableMount;
import dan200.computercraft.api.lua.IArguments;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.core.filesystem.MemoryMount;
import dan200.computercraft.core.terminal.Terminal;
import org.junit.jupiter.api.Assertions;
import java.util.Arrays;
import java.util.function.Consumer;
/**
* Helper class to run a program on a computer.
*/
public class ComputerBootstrap
{
private static final int TPS = 20;
public static final int MAX_TIME = 10;
public static void run( String program, Consumer<Computer> setup, int maxTimes )
{
MemoryMount mount = new MemoryMount()
.addFile( "test.lua", program )
.addFile( "startup.lua", "assertion.assert(pcall(loadfile('test.lua', nil, _ENV))) os.shutdown()" );
run( mount, setup, maxTimes );
}
public static void run( String program, int maxTimes )
{
run( program, x -> { }, maxTimes );
}
public static void run( IWritableMount mount, Consumer<Computer> setup, int maxTicks )
{
ComputerCraft.logPeripheralErrors = true;
ComputerCraft.maxMainComputerTime = ComputerCraft.maxMainGlobalTime = Integer.MAX_VALUE;
Terminal term = new Terminal( ComputerCraft.terminalWidth_computer, ComputerCraft.terminalHeight_computer );
final Computer computer = new Computer( new BasicEnvironment( mount ), term, 0 );
AssertApi api = new AssertApi();
computer.addApi( api );
setup.accept( computer );
try
{
computer.turnOn();
boolean everOn = false;
for( int tick = 0; tick < TPS * maxTicks; tick++ )
{
long start = System.currentTimeMillis();
computer.tick();
MainThread.executePendingTasks();
if( api.message != null )
{
ComputerCraft.log.debug( "Shutting down due to error" );
computer.shutdown();
Assertions.fail( api.message );
return;
}
long remaining = (1000 / TPS) - (System.currentTimeMillis() - start);
if( remaining > 0 ) Thread.sleep( remaining );
// Break if the computer was once on, and is now off.
everOn |= computer.isOn();
if( (everOn || tick > TPS) && !computer.isOn() ) break;
}
if( computer.isOn() || !api.didAssert )
{
StringBuilder builder = new StringBuilder().append( "Did not correctly [" );
if( !api.didAssert ) builder.append( " assert" );
if( computer.isOn() ) builder.append( " shutdown" );
builder.append( " ]\n" );
for( int line = 0; line < 19; line++ )
{
builder.append( String.format( "%2d | %" + term.getWidth() + "s |\n", line + 1, term.getLine( line ) ) );
}
computer.shutdown();
Assertions.fail( builder.toString() );
}
}
catch( InterruptedException ignored )
{
Thread.currentThread().interrupt();
}
}
public static class AssertApi implements ILuaAPI
{
boolean didAssert;
String message;
@Override
public String[] getNames()
{
return new String[] { "assertion" };
}
@LuaFunction
public final void log( IArguments arguments )
{
ComputerCraft.log.info( "[Computer] {}", Arrays.toString( arguments.getAll() ) );
}
@LuaFunction( "assert" )
public final Object[] doAssert( IArguments arguments ) throws LuaException
{
didAssert = true;
Object arg = arguments.get( 0 );
if( arg == null || arg == Boolean.FALSE )
{
message = arguments.optString( 1, "Assertion failed" );
throw new LuaException( message );
}
return arguments.getAll();
}
}
}

View File

@ -1,49 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.computer;
import com.google.common.io.CharStreams;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import static java.time.Duration.ofSeconds;
import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively;
public class ComputerTest
{
@Test
public void testTimeout()
{
assertTimeoutPreemptively( ofSeconds( 20 ), () -> {
try
{
ComputerBootstrap.run( "print('Hello') while true do end", ComputerBootstrap.MAX_TIME );
}
catch( AssertionError e )
{
if( e.getMessage().equals( "test.lua:1: Too long without yielding" ) ) return;
throw e;
}
Assertions.fail( "Expected computer to timeout" );
} );
}
public static void main( String[] args ) throws Exception
{
InputStream stream = ComputerTest.class.getClassLoader().getResourceAsStream( "benchmark.lua" );
try( InputStreamReader reader = new InputStreamReader( Objects.requireNonNull( stream ), StandardCharsets.UTF_8 ) )
{
String contents = CharStreams.toString( reader );
ComputerBootstrap.run( contents, 1000 );
}
}
}

View File

@ -1,81 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.filesystem;
import com.google.common.io.Files;
import dan200.computercraft.api.filesystem.IWritableMount;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.core.apis.ObjectWrapper;
import dan200.computercraft.core.apis.handles.EncodedWritableHandle;
import org.junit.jupiter.api.Test;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
public class FileSystemTest
{
private static final File ROOT = new File( "test-files/filesystem" );
private static final long CAPACITY = 1000000;
private static FileSystem mkFs() throws FileSystemException
{
IWritableMount writableMount = new FileMount( ROOT, CAPACITY );
return new FileSystem( "hdd", writableMount );
}
/**
* Ensures writing a file truncates it.
*
* @throws FileSystemException When the file system cannot be constructed.
* @throws LuaException When Lua functions fail.
* @throws IOException When reading and writing from strings
*/
@Test
public void testWriteTruncates() throws FileSystemException, LuaException, IOException
{
FileSystem fs = mkFs();
{
FileSystemWrapper<BufferedWriter> writer = fs.openForWrite( "out.txt", false, EncodedWritableHandle::openUtf8 );
ObjectWrapper wrapper = new ObjectWrapper( new EncodedWritableHandle( writer.get(), writer ) );
wrapper.call( "write", "This is a long line" );
wrapper.call( "close" );
}
assertEquals( "This is a long line", Files.asCharSource( new File( ROOT, "out.txt" ), StandardCharsets.UTF_8 ).read() );
{
FileSystemWrapper<BufferedWriter> writer = fs.openForWrite( "out.txt", false, EncodedWritableHandle::openUtf8 );
ObjectWrapper wrapper = new ObjectWrapper( new EncodedWritableHandle( writer.get(), writer ) );
wrapper.call( "write", "Tiny line" );
wrapper.call( "close" );
}
assertEquals( "Tiny line", Files.asCharSource( new File( ROOT, "out.txt" ), StandardCharsets.UTF_8 ).read() );
}
@Test
public void testUnmountCloses() throws FileSystemException
{
FileSystem fs = mkFs();
IWritableMount mount = new FileMount( new File( ROOT, "child" ), CAPACITY );
fs.mountWritable( "disk", "disk", mount );
FileSystemWrapper<BufferedWriter> writer = fs.openForWrite( "disk/out.txt", false, EncodedWritableHandle::openUtf8 );
ObjectWrapper wrapper = new ObjectWrapper( new EncodedWritableHandle( writer.get(), writer ) );
fs.unmount( "disk" );
LuaException err = assertThrows( LuaException.class, () -> wrapper.call( "write", "Tiny line" ) );
assertEquals( "attempt to use a closed file", err.getMessage() );
}
}

View File

@ -1,108 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.filesystem;
import com.google.common.io.ByteStreams;
import dan200.computercraft.api.filesystem.IMount;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import static org.junit.jupiter.api.Assertions.*;
public class JarMountTest
{
private static final File ZIP_FILE = new File( "test-files/jar-mount.zip" );
private static final FileTime MODIFY_TIME = FileTime.from( Instant.EPOCH.plus( 2, ChronoUnit.DAYS ) );
@BeforeAll
public static void before() throws IOException
{
ZIP_FILE.getParentFile().mkdirs();
try( ZipOutputStream stream = new ZipOutputStream( new FileOutputStream( ZIP_FILE ) ) )
{
stream.putNextEntry( new ZipEntry( "dir/" ) );
stream.closeEntry();
stream.putNextEntry( new ZipEntry( "dir/file.lua" ).setLastModifiedTime( MODIFY_TIME ) );
stream.write( "print('testing')".getBytes( StandardCharsets.UTF_8 ) );
stream.closeEntry();
}
}
@Test
public void mountsDir() throws IOException
{
IMount mount = new JarMount( ZIP_FILE, "dir" );
assertTrue( mount.isDirectory( "" ), "Root should be directory" );
assertTrue( mount.exists( "file.lua" ), "File should exist" );
}
@Test
public void mountsFile() throws IOException
{
IMount mount = new JarMount( ZIP_FILE, "dir/file.lua" );
assertTrue( mount.exists( "" ), "Root should exist" );
assertFalse( mount.isDirectory( "" ), "Root should be a file" );
}
@Test
public void opensFileFromFile() throws IOException
{
IMount mount = new JarMount( ZIP_FILE, "dir/file.lua" );
byte[] contents;
try( ReadableByteChannel stream = mount.openForRead( "" ) )
{
contents = ByteStreams.toByteArray( Channels.newInputStream( stream ) );
}
assertEquals( new String( contents, StandardCharsets.UTF_8 ), "print('testing')" );
}
@Test
public void opensFileFromDir() throws IOException
{
IMount mount = new JarMount( ZIP_FILE, "dir" );
byte[] contents;
try( ReadableByteChannel stream = mount.openForRead( "file.lua" ) )
{
contents = ByteStreams.toByteArray( Channels.newInputStream( stream ) );
}
assertEquals( new String( contents, StandardCharsets.UTF_8 ), "print('testing')" );
}
@Test
public void fileAttributes() throws IOException
{
BasicFileAttributes attributes = new JarMount( ZIP_FILE, "dir" ).getAttributes( "file.lua" );
assertFalse( attributes.isDirectory() );
assertEquals( "print('testing')".length(), attributes.size() );
assertEquals( MODIFY_TIME, attributes.lastModifiedTime() );
}
@Test
public void directoryAttributes() throws IOException
{
BasicFileAttributes attributes = new JarMount( ZIP_FILE, "dir" ).getAttributes( "" );
assertTrue( attributes.isDirectory() );
assertEquals( 0, attributes.size() );
}
}

View File

@ -1,148 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.filesystem;
import dan200.computercraft.api.filesystem.IWritableMount;
import dan200.computercraft.core.apis.handles.ArrayByteChannel;
import javax.annotation.Nonnull;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.util.*;
/**
* In-memory file mounts.
*/
public class MemoryMount implements IWritableMount
{
private final Map<String, byte[]> files = new HashMap<>();
private final Set<String> directories = new HashSet<>();
public MemoryMount()
{
directories.add( "" );
}
@Override
public void makeDirectory( @Nonnull String path )
{
File file = new File( path );
while( file != null )
{
directories.add( file.getPath() );
file = file.getParentFile();
}
}
@Override
public void delete( @Nonnull String path )
{
if( files.containsKey( path ) )
{
files.remove( path );
}
else
{
directories.remove( path );
for( String file : files.keySet().toArray( new String[0] ) )
{
if( file.startsWith( path ) )
{
files.remove( file );
}
}
File parent = new File( path ).getParentFile();
if( parent != null ) delete( parent.getPath() );
}
}
@Nonnull
@Override
public WritableByteChannel openForWrite( @Nonnull final String path )
{
return Channels.newChannel( new ByteArrayOutputStream()
{
@Override
public void close() throws IOException
{
super.close();
files.put( path, toByteArray() );
}
} );
}
@Nonnull
@Override
public WritableByteChannel openForAppend( @Nonnull final String path ) throws IOException
{
ByteArrayOutputStream stream = new ByteArrayOutputStream()
{
@Override
public void close() throws IOException
{
super.close();
files.put( path, toByteArray() );
}
};
byte[] current = files.get( path );
if( current != null ) stream.write( current );
return Channels.newChannel( stream );
}
@Override
public long getRemainingSpace()
{
return 1000000L;
}
@Override
public boolean exists( @Nonnull String path )
{
return files.containsKey( path ) || directories.contains( path );
}
@Override
public boolean isDirectory( @Nonnull String path )
{
return directories.contains( path );
}
@Override
public void list( @Nonnull String path, @Nonnull List<String> files )
{
for( String file : this.files.keySet() )
{
if( file.startsWith( path ) ) files.add( file.substring( path.length() + 1 ) );
}
}
@Override
public long getSize( @Nonnull String path )
{
throw new RuntimeException( "Not implemented" );
}
@Nonnull
@Override
public ReadableByteChannel openForRead( @Nonnull String path )
{
return new ArrayByteChannel( files.get( path ) );
}
public MemoryMount addFile( String file, String contents )
{
files.put( file, contents.getBytes() );
return this;
}
}

View File

@ -1,75 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.filesystem;
import dan200.computercraft.api.filesystem.IMount;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
public class ResourceMountTest
{
private IMount mount;
@BeforeEach
public void before()
{
// ReloadableResourceManager manager = new SimpleResourceReloadListener(ResourceType.SERVER_DATA);
// manager.addResourcePack( new FolderPack( new File( "src/main/resources" ) ) );
//
// mount = ResourceMount.get( "computercraft", "lua/rom", manager );
}
@Test
public void testList() throws IOException
{
List<String> files = new ArrayList<>();
mount.list( "", files );
files.sort( Comparator.naturalOrder() );
assertEquals(
Arrays.asList( "apis", "autorun", "help", "modules", "motd.txt", "programs", "startup.lua" ),
files
);
}
@Test
public void testExists() throws IOException
{
assertTrue( mount.exists( "" ) );
assertTrue( mount.exists( "startup.lua" ) );
assertTrue( mount.exists( "programs/fun/advanced/paint.lua" ) );
assertFalse( mount.exists( "programs/fun/advance/paint.lua" ) );
assertFalse( mount.exists( "programs/fun/advanced/paint.lu" ) );
}
@Test
public void testIsDir() throws IOException
{
assertTrue( mount.isDirectory( "" ) );
}
@Test
public void testIsFile() throws IOException
{
assertFalse( mount.isDirectory( "startup.lua" ) );
}
@Test
public void testSize() throws IOException
{
assertNotEquals( mount.getSize( "startup.lua" ), 0 );
}
}

View File

@ -1,150 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.terminal;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class TextBufferTest
{
@Test
void testStringConstructor()
{
TextBuffer textBuffer = new TextBuffer( "test" );
assertEquals( "test", textBuffer.toString() );
}
@Test
void testCharRepetitionConstructor()
{
TextBuffer textBuffer = new TextBuffer( 'a', 5 );
assertEquals( "aaaaa", textBuffer.toString() );
}
@Test
void testLength()
{
TextBuffer textBuffer = new TextBuffer( "test" );
assertEquals( 4, textBuffer.length() );
}
@Test
void testWrite()
{
TextBuffer textBuffer = new TextBuffer( ' ', 4 );
textBuffer.write( "test" );
assertEquals( "test", textBuffer.toString() );
}
@Test
void testWriteTextBuffer()
{
TextBuffer source = new TextBuffer( "test" );
TextBuffer target = new TextBuffer( " " );
target.write( source );
assertEquals( "test", target.toString() );
}
@Test
void testWriteFromPos()
{
TextBuffer textBuffer = new TextBuffer( "test" );
textBuffer.write( "il", 1 );
assertEquals( "tilt", textBuffer.toString() );
}
@Test
void testWriteOutOfBounds()
{
TextBuffer textBuffer = new TextBuffer( "test" );
textBuffer.write( "abcdefghijklmnop", -5 );
assertEquals( "fghi", textBuffer.toString() );
}
@Test
void testWriteOutOfBounds2()
{
TextBuffer textBuffer = new TextBuffer( " " );
textBuffer.write( "Hello, world!", -3 );
assertEquals( "lo, world! ", textBuffer.toString() );
}
@Test
void testFill()
{
TextBuffer textBuffer = new TextBuffer( "test" );
textBuffer.fill( 'c' );
assertEquals( "cccc", textBuffer.toString() );
}
@Test
void testFillSubstring()
{
TextBuffer textBuffer = new TextBuffer( "test" );
textBuffer.fill( 'c', 1, 3 );
assertEquals( "tcct", textBuffer.toString() );
}
@Test
void testFillOutOfBounds()
{
TextBuffer textBuffer = new TextBuffer( "test" );
textBuffer.fill( 'c', -5, 5 );
assertEquals( "cccc", textBuffer.toString() );
}
@Test
void testCharAt()
{
TextBuffer textBuffer = new TextBuffer( "test" );
assertEquals( 'e', textBuffer.charAt( 1 ) );
}
@Test
void testSetChar()
{
TextBuffer textBuffer = new TextBuffer( "test" );
textBuffer.setChar( 2, 'n' );
assertEquals( "tent", textBuffer.toString() );
}
@Test
void testSetCharWithNegativeIndex()
{
TextBuffer textBuffer = new TextBuffer( "test" );
textBuffer.setChar( -5, 'n' );
assertEquals( "test", textBuffer.toString(), "Buffer should not change after setting char with negative index." );
}
@Test
void testSetCharWithIndexBeyondBufferEnd()
{
TextBuffer textBuffer = new TextBuffer( "test" );
textBuffer.setChar( 10, 'n' );
assertEquals( "test", textBuffer.toString(), "Buffer should not change after setting char beyond buffer end." );
}
@Test
void testMultipleOperations()
{
TextBuffer textBuffer = new TextBuffer( ' ', 5 );
textBuffer.setChar( 0, 'H' );
textBuffer.setChar( 1, 'e' );
textBuffer.setChar( 2, 'l' );
textBuffer.write( "lo", 3 );
assertEquals( "Hello", textBuffer.toString(), "TextBuffer failed to persist over multiple operations." );
}
@Test
void testEmptyBuffer()
{
TextBuffer textBuffer = new TextBuffer( "" );
// exception on writing to empty buffer would fail the test
textBuffer.write( "test" );
assertEquals( "", textBuffer.toString() );
}
}

View File

@ -1,463 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared.wired;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.network.wired.IWiredElement;
import dan200.computercraft.api.network.wired.IWiredNetwork;
import dan200.computercraft.api.network.wired.IWiredNetworkChange;
import dan200.computercraft.api.network.wired.IWiredNode;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.shared.util.DirectionUtil;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import static org.junit.jupiter.api.Assertions.*;
public class NetworkTest
{
@Test
public void testConnect()
{
NetworkElement
aE = new NetworkElement( null, null, "a" ),
bE = new NetworkElement( null, null, "b" ),
cE = new NetworkElement( null, null, "c" );
IWiredNode
aN = aE.getNode(),
bN = bE.getNode(),
cN = cE.getNode();
assertNotEquals( aN.getNetwork(), bN.getNetwork(), "A's and B's network must be different" );
assertNotEquals( aN.getNetwork(), cN.getNetwork(), "A's and C's network must be different" );
assertNotEquals( bN.getNetwork(), cN.getNetwork(), "B's and C's network must be different" );
assertTrue( aN.getNetwork().connect( aN, bN ), "Must be able to add connection" );
assertFalse( aN.getNetwork().connect( aN, bN ), "Cannot add connection twice" );
assertEquals( aN.getNetwork(), bN.getNetwork(), "A's and B's network must be equal" );
assertEquals( Sets.newHashSet( aN, bN ), nodes( aN.getNetwork() ), "A's network should be A and B" );
assertEquals( Sets.newHashSet( "a", "b" ), aE.allPeripherals().keySet(), "A's peripheral set should be A, B" );
assertEquals( Sets.newHashSet( "a", "b" ), bE.allPeripherals().keySet(), "B's peripheral set should be A, B" );
aN.getNetwork().connect( aN, cN );
assertEquals( aN.getNetwork(), bN.getNetwork(), "A's and B's network must be equal" );
assertEquals( aN.getNetwork(), cN.getNetwork(), "A's and C's network must be equal" );
assertEquals( Sets.newHashSet( aN, bN, cN ), nodes( aN.getNetwork() ), "A's network should be A, B and C" );
assertEquals( Sets.newHashSet( bN, cN ), neighbours( aN ), "A's neighbour set should be B, C" );
assertEquals( Sets.newHashSet( aN ), neighbours( bN ), "B's neighbour set should be A" );
assertEquals( Sets.newHashSet( aN ), neighbours( cN ), "C's neighbour set should be A" );
assertEquals( Sets.newHashSet( "a", "b", "c" ), aE.allPeripherals().keySet(), "A's peripheral set should be A, B, C" );
assertEquals( Sets.newHashSet( "a", "b", "c" ), bE.allPeripherals().keySet(), "B's peripheral set should be A, B, C" );
assertEquals( Sets.newHashSet( "a", "b", "c" ), cE.allPeripherals().keySet(), "C's peripheral set should be A, B, C" );
}
@Test
public void testDisconnectNoChange()
{
NetworkElement
aE = new NetworkElement( null, null, "a" ),
bE = new NetworkElement( null, null, "b" ),
cE = new NetworkElement( null, null, "c" );
IWiredNode
aN = aE.getNode(),
bN = bE.getNode(),
cN = cE.getNode();
aN.getNetwork().connect( aN, bN );
aN.getNetwork().connect( aN, cN );
aN.getNetwork().connect( bN, cN );
aN.getNetwork().disconnect( aN, bN );
assertEquals( aN.getNetwork(), bN.getNetwork(), "A's and B's network must be equal" );
assertEquals( aN.getNetwork(), cN.getNetwork(), "A's and C's network must be equal" );
assertEquals( Sets.newHashSet( aN, bN, cN ), nodes( aN.getNetwork() ), "A's network should be A, B and C" );
assertEquals( Sets.newHashSet( "a", "b", "c" ), aE.allPeripherals().keySet(), "A's peripheral set should be A, B, C" );
assertEquals( Sets.newHashSet( "a", "b", "c" ), bE.allPeripherals().keySet(), "B's peripheral set should be A, B, C" );
assertEquals( Sets.newHashSet( "a", "b", "c" ), cE.allPeripherals().keySet(), "C's peripheral set should be A, B, C" );
}
@Test
public void testDisconnectLeaf()
{
NetworkElement
aE = new NetworkElement( null, null, "a" ),
bE = new NetworkElement( null, null, "b" ),
cE = new NetworkElement( null, null, "c" );
IWiredNode
aN = aE.getNode(),
bN = bE.getNode(),
cN = cE.getNode();
aN.getNetwork().connect( aN, bN );
aN.getNetwork().connect( aN, cN );
aN.getNetwork().disconnect( aN, bN );
assertNotEquals( aN.getNetwork(), bN.getNetwork(), "A's and B's network must not be equal" );
assertEquals( aN.getNetwork(), cN.getNetwork(), "A's and C's network must be equal" );
assertEquals( Sets.newHashSet( aN, cN ), nodes( aN.getNetwork() ), "A's network should be A and C" );
assertEquals( Sets.newHashSet( bN ), nodes( bN.getNetwork() ), "B's network should be B" );
assertEquals( Sets.newHashSet( "a", "c" ), aE.allPeripherals().keySet(), "A's peripheral set should be A, C" );
assertEquals( Sets.newHashSet( "b" ), bE.allPeripherals().keySet(), "B's peripheral set should be B" );
assertEquals( Sets.newHashSet( "a", "c" ), cE.allPeripherals().keySet(), "C's peripheral set should be A, C" );
}
@Test
public void testDisconnectSplit()
{
NetworkElement
aE = new NetworkElement( null, null, "a" ),
aaE = new NetworkElement( null, null, "a_" ),
bE = new NetworkElement( null, null, "b" ),
bbE = new NetworkElement( null, null, "b_" );
IWiredNode
aN = aE.getNode(),
aaN = aaE.getNode(),
bN = bE.getNode(),
bbN = bbE.getNode();
aN.getNetwork().connect( aN, aaN );
bN.getNetwork().connect( bN, bbN );
aN.getNetwork().connect( aN, bN );
aN.getNetwork().disconnect( aN, bN );
assertNotEquals( aN.getNetwork(), bN.getNetwork(), "A's and B's network must not be equal" );
assertEquals( aN.getNetwork(), aaN.getNetwork(), "A's and A_'s network must be equal" );
assertEquals( bN.getNetwork(), bbN.getNetwork(), "B's and B_'s network must be equal" );
assertEquals( Sets.newHashSet( aN, aaN ), nodes( aN.getNetwork() ), "A's network should be A and A_" );
assertEquals( Sets.newHashSet( bN, bbN ), nodes( bN.getNetwork() ), "B's network should be B and B_" );
assertEquals( Sets.newHashSet( "a", "a_" ), aE.allPeripherals().keySet(), "A's peripheral set should be A and A_" );
assertEquals( Sets.newHashSet( "b", "b_" ), bE.allPeripherals().keySet(), "B's peripheral set should be B and B_" );
}
@Test
public void testRemoveSingle()
{
NetworkElement aE = new NetworkElement( null, null, "a" );
IWiredNode aN = aE.getNode();
IWiredNetwork network = aN.getNetwork();
assertFalse( aN.remove(), "Cannot remove node from an empty network" );
assertEquals( network, aN.getNetwork(), "Networks are same before and after" );
}
@Test
public void testRemoveLeaf()
{
NetworkElement
aE = new NetworkElement( null, null, "a" ),
bE = new NetworkElement( null, null, "b" ),
cE = new NetworkElement( null, null, "c" );
IWiredNode
aN = aE.getNode(),
bN = bE.getNode(),
cN = cE.getNode();
aN.getNetwork().connect( aN, bN );
aN.getNetwork().connect( aN, cN );
assertTrue( aN.getNetwork().remove( bN ), "Must be able to remove node" );
assertFalse( aN.getNetwork().remove( bN ), "Cannot remove a second time" );
assertNotEquals( aN.getNetwork(), bN.getNetwork(), "A's and B's network must not be equal" );
assertEquals( aN.getNetwork(), cN.getNetwork(), "A's and C's network must be equal" );
assertEquals( Sets.newHashSet( aN, cN ), nodes( aN.getNetwork() ), "A's network should be A and C" );
assertEquals( Sets.newHashSet( bN ), nodes( bN.getNetwork() ), "B's network should be B" );
assertEquals( Sets.newHashSet( "a", "c" ), aE.allPeripherals().keySet(), "A's peripheral set should be A, C" );
assertEquals( Sets.newHashSet(), bE.allPeripherals().keySet(), "B's peripheral set should be empty" );
assertEquals( Sets.newHashSet( "a", "c" ), cE.allPeripherals().keySet(), "C's peripheral set should be A, C" );
}
@Test
public void testRemoveSplit()
{
NetworkElement
aE = new NetworkElement( null, null, "a" ),
aaE = new NetworkElement( null, null, "a_" ),
bE = new NetworkElement( null, null, "b" ),
bbE = new NetworkElement( null, null, "b_" ),
cE = new NetworkElement( null, null, "c" );
IWiredNode
aN = aE.getNode(),
aaN = aaE.getNode(),
bN = bE.getNode(),
bbN = bbE.getNode(),
cN = cE.getNode();
aN.getNetwork().connect( aN, aaN );
bN.getNetwork().connect( bN, bbN );
cN.getNetwork().connect( aN, cN );
cN.getNetwork().connect( bN, cN );
cN.getNetwork().remove( cN );
assertNotEquals( aN.getNetwork(), bN.getNetwork(), "A's and B's network must not be equal" );
assertEquals( aN.getNetwork(), aaN.getNetwork(), "A's and A_'s network must be equal" );
assertEquals( bN.getNetwork(), bbN.getNetwork(), "B's and B_'s network must be equal" );
assertEquals( Sets.newHashSet( aN, aaN ), nodes( aN.getNetwork() ), "A's network should be A and A_" );
assertEquals( Sets.newHashSet( bN, bbN ), nodes( bN.getNetwork() ), "B's network should be B and B_" );
assertEquals( Sets.newHashSet( cN ), nodes( cN.getNetwork() ), "C's network should be C" );
assertEquals( Sets.newHashSet( "a", "a_" ), aE.allPeripherals().keySet(), "A's peripheral set should be A and A_" );
assertEquals( Sets.newHashSet( "b", "b_" ), bE.allPeripherals().keySet(), "B's peripheral set should be B and B_" );
assertEquals( Sets.newHashSet(), cE.allPeripherals().keySet(), "C's peripheral set should be empty" );
}
private static final int BRUTE_SIZE = 16;
private static final int TOGGLE_CONNECTION_TIMES = 5;
private static final int TOGGLE_NODE_TIMES = 5;
@Test
@Disabled( "Takes a long time to run, mostly for stress testing" )
public void testLarge()
{
Grid<IWiredNode> grid = new Grid<>( BRUTE_SIZE );
grid.map( ( existing, pos ) -> new NetworkElement( null, null, "n_" + pos ).getNode() );
// Test connecting
{
long start = System.nanoTime();
grid.forEach( ( existing, pos ) -> {
for( Direction facing : DirectionUtil.FACINGS )
{
BlockPos offset = pos.offset( facing );
if( offset.getX() > BRUTE_SIZE / 2 == pos.getX() > BRUTE_SIZE / 2 )
{
IWiredNode other = grid.get( offset );
if( other != null ) existing.getNetwork().connect( existing, other );
}
}
} );
long end = System.nanoTime();
System.out.printf( "Connecting %s³ nodes took %s seconds\n", BRUTE_SIZE, (end - start) * 1e-9 );
}
// Test toggling
{
IWiredNode left = grid.get( new BlockPos( BRUTE_SIZE / 2, 0, 0 ) );
IWiredNode right = grid.get( new BlockPos( BRUTE_SIZE / 2 + 1, 0, 0 ) );
assertNotEquals( left.getNetwork(), right.getNetwork() );
long start = System.nanoTime();
for( int i = 0; i < TOGGLE_CONNECTION_TIMES; i++ )
{
left.getNetwork().connect( left, right );
left.getNetwork().disconnect( left, right );
}
long end = System.nanoTime();
System.out.printf( "Toggling connection %s times took %s seconds\n", TOGGLE_CONNECTION_TIMES, (end - start) * 1e-9 );
}
{
IWiredNode left = grid.get( new BlockPos( BRUTE_SIZE / 2, 0, 0 ) );
IWiredNode right = grid.get( new BlockPos( BRUTE_SIZE / 2 + 1, 0, 0 ) );
IWiredNode centre = new NetworkElement( null, null, "c" ).getNode();
assertNotEquals( left.getNetwork(), right.getNetwork() );
long start = System.nanoTime();
for( int i = 0; i < TOGGLE_NODE_TIMES; i++ )
{
left.getNetwork().connect( left, centre );
right.getNetwork().connect( right, centre );
left.getNetwork().remove( centre );
}
long end = System.nanoTime();
System.out.printf( "Toggling node %s times took %s seconds\n", TOGGLE_NODE_TIMES, (end - start) * 1e-9 );
}
}
private static final class NetworkElement implements IWiredElement
{
private final World world;
private final Vec3d position;
private final String id;
private final IWiredNode node;
private final Map<String, IPeripheral> localPeripherals = Maps.newHashMap();
private final Map<String, IPeripheral> remotePeripherals = Maps.newHashMap();
private NetworkElement( World world, Vec3d position, String id )
{
this.world = world;
this.position = position;
this.id = id;
this.node = ComputerCraftAPI.createWiredNodeForElement( this );
this.addPeripheral( id );
}
@Nonnull
@Override
public World getWorld()
{
return world;
}
@Nonnull
@Override
public Vec3d getPosition()
{
return position;
}
@Nonnull
@Override
public String getSenderID()
{
return id;
}
@Override
public String toString()
{
return "NetworkElement{" + id + "}";
}
@Nonnull
@Override
public IWiredNode getNode()
{
return node;
}
@Override
public void networkChanged( @Nonnull IWiredNetworkChange change )
{
remotePeripherals.keySet().removeAll( change.peripheralsRemoved().keySet() );
remotePeripherals.putAll( change.peripheralsAdded() );
}
public NetworkElement addPeripheral( String name )
{
localPeripherals.put( name, new NetworkPeripheral() );
getNode().updatePeripherals( localPeripherals );
return this;
}
@Nonnull
public Map<String, IPeripheral> allPeripherals()
{
return remotePeripherals;
}
}
private static class NetworkPeripheral implements IPeripheral
{
@Nonnull
@Override
public String getType()
{
return "test";
}
@Override
public boolean equals( @Nullable IPeripheral other )
{
return this == other;
}
}
private static class Grid<T>
{
private final int size;
private final T[] box;
@SuppressWarnings( "unchecked" )
Grid( int size )
{
this.size = size;
this.box = (T[]) new Object[size * size * size];
}
public T get( BlockPos pos )
{
int x = pos.getX(), y = pos.getY(), z = pos.getZ();
return x >= 0 && x < size && y >= 0 && y < size && z >= 0 && z < size
? box[x * size * size + y * size + z]
: null;
}
public void forEach( BiConsumer<T, BlockPos> transform )
{
for( int x = 0; x < size; x++ )
{
for( int y = 0; y < size; y++ )
{
for( int z = 0; z < size; z++ )
{
transform.accept( box[x * size * size + y * size + z], new BlockPos( x, y, z ) );
}
}
}
}
public void map( BiFunction<T, BlockPos, T> transform )
{
for( int x = 0; x < size; x++ )
{
for( int y = 0; y < size; y++ )
{
for( int z = 0; z < size; z++ )
{
box[x * size * size + y * size + z] = transform.apply( box[x * size * size + y * size + z], new BlockPos( x, y, z ) );
}
}
}
}
}
private static Set<WiredNode> nodes( IWiredNetwork network )
{
return ((WiredNetwork) network).nodes;
}
private static Set<WiredNode> neighbours( IWiredNode node )
{
return ((WiredNode) node).neighbours;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,504 +0,0 @@
--- A very basic test framework for ComputerCraft
--
-- Like Busted (http://olivinelabs.com/busted/), but more memorable.
--
-- @usage
-- describe("something to test", function()
-- it("some property", function()
-- expect(some_function()):equals("What it should equal")
-- end)
-- end)
--- Assert an argument to the given function has the specified type.
--
-- @tparam string The function's name
-- @tparam int The argument index to this function
-- @tparam string The type this argument should have. May be 'value' for any
-- non-nil value.
-- @param val The value to check
-- @raise If this value doesn't match the expected type.
local function check(func, arg, ty, val)
if ty == 'value' then
if val == nil then
error(('%s: bad argument #%d (got nil)'):format(func, arg), 3)
end
elseif type(val) ~= ty then
return error(('%s: bad argument #%d (expected %s, got %s)'):format(func, arg, ty, type(val)), 3)
end
end
local active_stubs = {}
--- Stub a global variable with a specific value
--
-- @tparam string var The variable to stub
-- @param value The value to stub it with
local function stub(tbl, var, value)
table.insert(active_stubs, { tbl = tbl, var = var, value = tbl[var] })
_G[var] = value
end
--- Capture the current global state of the computer
local function push_state()
local stubs = active_stubs
active_stubs = {}
return {
term = term.current(),
input = io.input(),
output = io.output(),
stubs = stubs,
}
end
--- Restore the global state of the computer to a previous version
local function pop_state(state)
for i = #active_stubs, 1, -1 do
local stub = active_stubs[i]
stub.tbl[stub.var] = stub.value
end
active_stubs = state.stubs
term.redirect(state.term)
io.input(state.input)
io.output(state.output)
end
local error_mt = { __tostring = function(self) return self.message end }
--- Attempt to execute the provided function, gathering a stack trace when it
-- errors.
--
-- @tparam
-- @return[1] true
-- @return[2] false
-- @return[2] The error object
local function try(fn)
if not debug or not debug.traceback then
local ok, err = pcall(fn)
if ok or getmetatable(err) == error_mt then
return ok, err
else
return ok, setmetatable({ message = tostring(err) }, error_mt)
end
end
local ok, err = xpcall(fn, function(err)
return { message = err, trace = debug.traceback(nil, 2) }
end)
-- If we succeeded, propagate it
if ok then return ok, err end
-- Error handling failed for some reason - just return a simpler error
if type(err) ~= "table" then
return ok, setmetatable({ message = tostring(err) }, error_mt)
end
-- Find the common substring the errors' trace and the current one. Then
-- eliminate it.
local trace = debug.traceback()
for i = 1, #trace do
if trace:sub(-i) ~= err.trace:sub(-i) then
err.trace = err.trace:sub(1, -i)
break
end
end
-- If we've received a rethrown error, copy
if getmetatable(err.message) == error_mt then
for k, v in pairs(err.message) do err[k] = v end
return ok, err
end
return ok, setmetatable(err, error_mt)
end
--- Fail a test with the given message
--
-- @tparam string message The message to fail with
-- @raises An error with the given message
local function fail(message)
check('fail', 1, 'string', message)
error(setmetatable({ message = message, fail = true }, error_mt))
end
--- Format an object in order to make it more readable
--
-- @param value The value to format
-- @treturn string The formatted value
local function format(value)
-- TODO: Look into something like mbs's pretty printer.
local ok, res = pcall(textutils.serialise, value)
if ok then return res else return tostring(value) end
end
local expect_mt = {}
expect_mt.__index = expect_mt
--- Assert that this expectation has the provided value
--
-- @param value The value to require this expectation to be equal to
-- @raises If the values are not equal
function expect_mt:equals(value)
if value ~= self.value then
fail(("Expected %s\n but got %s"):format(format(value), format(self.value)))
end
return self
end
expect_mt.equal = expect_mt.equals
expect_mt.eq = expect_mt.equals
--- Assert that this expectation does not equal the provided value
--
-- @param value The value to require this expectation to not be equal to
-- @raises If the values are equal
function expect_mt:not_equals(value)
if value == self.value then
fail(("Expected any value but %s"):format(format(value)))
end
return self
end
expect_mt.not_equal = expect_mt.not_equals
expect_mt.ne = expect_mt.not_equals
--- Assert that this expectation has something of the provided type
--
-- @tparam string exp_type The type to require this expectation to have
-- @raises If it does not have that thpe
function expect_mt:type(exp_type)
local actual_type = type(self.value)
if exp_type ~= actual_type then
fail(("Expected value of type %s\n got %s"):format(exp_type, actual_type))
end
return self
end
local function matches(eq, exact, left, right)
if left == right then return true end
local ty = type(left)
if ty ~= type(right) or ty ~= "table" then return false end
-- If we've already explored/are exploring the left and right then return
if eq[left] and eq[left][right] then return true end
if not eq[left] then eq[left] = {[right] = true} else eq[left][right] = true end
if not eq[right] then eq[right] = {[left] = true} else eq[right][left] = true end
-- Verify all pairs in left are equal to those in right
for k, v in pairs(left) do
if not matches(eq, exact, v, right[k]) then return false end
end
if exact then
-- And verify all pairs in right are present in left
for k in pairs(right) do
if left[k] == nil then return false end
end
end
return true
end
--- Assert that this expectation is structurally equivalent to
-- the provided object.
--
-- @param value The value to check for structural equivalence
-- @raises If they are not equivalent
function expect_mt:same(value)
if not matches({}, true, self.value, value) then
fail(("Expected %s\n but got %s"):format(format(value), format(self.value)))
end
return self
end
--- Assert that this expectation contains all fields mentioned
-- in the provided object.
--
-- @param value The value to check against
-- @raises If this does not match the provided value
function expect_mt:matches(value)
if not matches({}, false, value, self.value) then
fail(("Expected %s\nto match %s"):format(format(self.value), format(value)))
end
return self
end
local expect = setmetatable( {
--- Construct an expectation on the error message calling this function
-- produces
--
-- @tparam fun The function to call
-- @param ... The function arguments
-- @return The new expectation
error = function(fun, ...)
local ok, res = pcall(fun, ...) local _, line = pcall(error, "", 2)
if ok then fail("expected function to error") end
if res:sub(1, #line) == line then
res = res:sub(#line + 1)
elseif res:sub(1, 7) == "pcall: " then
res = res:sub(8)
end
return setmetatable({ value = res }, expect_mt)
end,
}, {
--- Construct a new expectation from the provided value
--
-- @param value The value to apply assertions to
-- @return The new expectation
__call = function(_, value)
return setmetatable({ value = value }, expect_mt)
end
})
--- The stack of "describe"s.
local test_stack = { n = 0 }
--- Whether we're now running tests, and so cannot run any more.
local tests_locked = false
--- The list of tests that we'll run
local test_list, test_map, test_count = { }, { }, 0
--- Add a new test to our queue.
--
-- @param test The descriptor of this test
local function do_test(test)
-- Set the name if it doesn't already exist
if not test.name then test.name = table.concat(test_stack, "\0", 1, test_stack.n) end
test_count = test_count + 1
test_list[test_count] = test
test_map[test.name] = test_count
end
--- Get the "friendly" name of this test.
--
-- @treturn string This test's friendly name
local function test_name(test) return (test.name:gsub("\0", " \26 ")) end
--- Describe something which will be tested, such as a function or situation
--
-- @tparam string name The name of the object to test
-- @tparam function body A function which describes the tests for this object.
local function describe(name, body)
check('describe', 1, 'string', name)
check('describe', 2, 'function', body)
if tests_locked then error("Cannot describe something while running tests", 2) end
-- Push our name onto the stack, eval and pop it
local n = test_stack.n + 1
test_stack[n], test_stack.n = name, n
local ok, err = try(body)
-- We count errors as a (failing) test.
if not ok then do_test { error = err } end
test_stack.n = n - 1
end
--- Declare a single test within a context
--
-- @tparam string name What you are testing
-- @tparam function body A function which runs the test, failing if it does
-- the assertions are not met.
local function it(name, body)
check('it', 1, 'string', name)
check('it', 2, 'function', body)
if tests_locked then error("Cannot create test while running tests", 2) end
-- Push name onto the stack
local n = test_stack.n + 1
test_stack[n], test_stack.n, tests_locked = name, n, true
do_test { action = body }
-- Pop the test from the stack
test_stack.n, tests_locked = n - 1, false
end
--- Declare a single not-yet-implemented test
--
-- @tparam string name What you really should be testing but aren't
local function pending(name)
check('it', 1, 'string', name)
if tests_locked then error("Cannot create test while running tests", 2) end
local _, loc = pcall(error, "", 3)
loc = loc:gsub(":%s*$", "")
local n = test_stack.n + 1
test_stack[n], test_stack.n = name, n
do_test { pending = true, trace = loc }
test_stack.n = n - 1
end
local arg = ...
if arg == "--help" or arg == "-h" then
io.write("Usage: mcfly [DIR]\n")
io.write("\n")
io.write("Run tests in the provided DIRectory, or `spec` if not given.")
return
end
local root_dir = shell.resolve(arg or "spec")
if not fs.isDir(root_dir) then
io.stderr:write(("%q is not a directory.\n"):format(root_dir))
error()
end
do
-- Load in the tests from all our files
local env = setmetatable({}, { __index = _ENV })
local function set_env(tbl)
for k in pairs(env) do env[k] = nil end
for k, v in pairs(tbl) do env[k] = v end
end
-- When declaring tests, you shouldn't be able to use test methods
set_env { describe = describe, it = it, pending = pending }
local suffix = "_spec.lua"
local function run_in(sub_dir)
for _, name in ipairs(fs.list(sub_dir)) do
local file = fs.combine(sub_dir, name)
if fs.isDir(file) then
run_in(file)
elseif file:sub(-#suffix) == suffix then
local fun, err = loadfile(file, env)
if not fun then
do_test { name = file:sub(#root_dir + 2), error = { message = err } }
else
local ok, err = try(fun)
if not ok then do_test { name = file:sub(#root_dir + 2), error = err } end
end
end
end
end
run_in(root_dir)
-- When running tests, you shouldn't be able to declare new ones.
set_env { expect = expect, fail = fail, stub = stub }
end
-- Error if we've found no tests
if test_count == 0 then
io.stderr:write(("Could not find any tests in %q\n"):format(root_dir))
error()
end
-- The results of each test, as well as how many passed and the count.
local test_results, test_status, tests_run = { n = 0 }, {}, 0
-- All possible test statuses
local statuses = {
pass = { desc = "Pass", col = colours.green, dot = "\7" }, -- Circle
fail = { desc = "Failed", col = colours.red, dot = "\4" }, -- Diamond
error = { desc = "Error", col = colours.magenta, dot = "\4" },
pending = { desc = "Pending", col = colours.yellow, dot = "\186" }, -- Hollow circle
}
-- Set up each test status count.
for k in pairs(statuses) do test_status[k] = 0 end
--- Do the actual running of our test
local function do_run(test)
-- If we're a pre-computed test, determine our status message. Otherwise,
-- skip.
local status, err
if test.pending then
status = "pending"
elseif test.error then
err = test.error
status = "error"
elseif test.action then
local state = push_state()
local ok
ok, err = try(test.action)
status = ok and "pass" or (err.fail and "fail" or "error")
pop_state(state)
end
-- If we've a boolean status, then convert it into a string
if status == true then status = "pass"
elseif status == false then status = err.fail and "fail" or "error"
end
tests_run = tests_run + 1
test_status[status] = test_status[status] + 1
test_results[tests_run] = {
status = status, name = test.name,
message = test.message or err and err.message,
trace = test.trace or err and err.trace,
}
-- If we're running under howlci, then log some info.
if howlci then howlci.status(status, test_name(test)) end
if cct_test then cct_test.submit(test_results[tests_run]) end
-- Print our progress dot
local data = statuses[status]
term.setTextColour(data.col) io.write(data.dot)
term.setTextColour(colours.white)
end
-- Loop over all our tests, running them as required.
if cct_test then
-- If we're within a cct_test environment, then submit them and wait on tests
-- to be run.
cct_test.start(test_map)
while true do
local _, name = os.pullEvent("cct_test_run")
if not name then break end
do_run(test_list[test_map[name]])
end
else
for _, test in pairs(test_list) do do_run(test) end
end
-- Otherwise, display the results of each failure
io.write("\n\n")
for i = 1, tests_run do
local test = test_results[i]
if test.status ~= "pass" then
local status_data = statuses[test.status]
term.setTextColour(status_data.col)
io.write(status_data.desc)
term.setTextColour(colours.white)
io.write(" \26 " .. test_name(test) .. "\n")
if test.message then
io.write(" " .. test.message:gsub("\n", "\n ") .. "\n")
end
if test.trace then
term.setTextColour(colours.lightGrey)
io.write(" " .. test.trace:gsub("\n", "\n ") .. "\n")
end
io.write("\n")
end
end
-- And some summary statistics
local actual_count = tests_run - test_status.pending
local info = ("Ran %s test(s), of which %s passed (%g%%).")
:format(actual_count, test_status.pass, (test_status.pass / actual_count) * 100)
if test_status.pending > 0 then
info = info .. (" Skipped %d pending test(s)."):format(test_status.pending)
end
term.setTextColour(colours.white) io.write(info .. "\n")
if howlci then howlci.log("debug", info) sleep(3) end

View File

@ -1,92 +0,0 @@
describe("The colors library", function()
describe("colors.combine", function()
it("validates arguments", function()
expect.error(colors.combine, 1, nil):eq("bad argument #2 (expected number, got nil)")
expect.error(colors.combine, 1, 1, nil):eq("bad argument #3 (expected number, got nil)")
end)
it("combines colours", function()
expect(colors.combine()):eq(0)
expect(colors.combine(colors.red, colors.brown, colors.green)):eq(0x7000)
end)
end)
describe("colors.subtract", function()
it("validates arguments", function()
expect.error(colors.subtract, nil):eq("bad argument #1 (expected number, got nil)")
expect.error(colors.subtract, 1, nil):eq("bad argument #2 (expected number, got nil)")
expect.error(colors.subtract, 1, 1, nil):eq("bad argument #3 (expected number, got nil)")
end)
it("subtracts colours", function()
expect(colors.subtract(0x7000, colors.green)):equals(0x5000)
expect(colors.subtract(0x5000, colors.red)):equals(0x1000)
end)
it("does nothing when color is not present", function()
expect(colors.subtract(0x1000, colors.red)):equals(0x1000)
end)
it("accepts multiple arguments", function()
expect(colors.subtract(0x7000, colors.red, colors.green, colors.red)):equals(0x1000)
end)
end)
describe("colors.test", function()
it("validates arguments", function()
expect.error(colors.test, nil):eq("bad argument #1 (expected number, got nil)")
expect.error(colors.test, 1, nil):eq("bad argument #2 (expected number, got nil)")
end)
it("returns true when present", function()
expect(colors.test(0x7000, colors.green)):equals(true)
end)
it("returns false when absent", function()
expect(colors.test(0x5000, colors.green)):equals(false)
end)
it("allows multiple colors", function()
expect(colors.test(0x7000, 0x5000)):equals(true)
end)
end)
describe("colors.packRGB", function()
it("validates arguments", function()
expect.error(colors.packRGB, nil):eq("bad argument #1 (expected number, got nil)")
expect.error(colors.packRGB, 1, nil):eq("bad argument #2 (expected number, got nil)")
expect.error(colors.packRGB, 1, 1, nil):eq("bad argument #3 (expected number, got nil)")
end)
it("packs colours", function()
expect(colors.packRGB(0.3, 0.5, 0.6)):equals(0x4c7f99)
end)
end)
describe("colors.unpackRGB", function()
it("validates arguments", function()
expect.error(colors.unpackRGB, nil):eq("bad argument #1 (expected number, got nil)")
end)
it("unpacks colours", function()
expect({ colors.unpackRGB(0x4c7f99) }):same { 0x4c / 0xFF, 0x7f / 0xFF, 0.6 }
end)
end)
it("colors.rgb8", function()
expect(colors.rgb8(0.3, 0.5, 0.6)):equals(0x4c7f99)
expect({ colors.rgb8(0x4c7f99) }):same { 0x4c / 0xFF, 0x7f / 0xFF, 0.6 }
end)
describe("colors.toBlit", function()
it("validates arguments", function()
expect.error(colors.toBlit, nil):eq("bad argument #1 (expected number, got nil)")
end)
it("converts all colors", function()
for i = 0, 15 do
expect(colors.toBlit(2 ^ i)):eq(string.format("%x", i))
end
end)
it("floors colors", function()
expect(colors.toBlit(16385)):eq("e")
end)
end)
end)

View File

@ -1,222 +0,0 @@
describe("The fs library", function()
describe("fs.complete", function()
it("validates arguments", function()
fs.complete("", "")
fs.complete("", "", true)
fs.complete("", "", nil, true)
expect.error(fs.complete, nil):eq("bad argument #1 (expected string, got nil)")
expect.error(fs.complete, "", nil):eq("bad argument #2 (expected string, got nil)")
expect.error(fs.complete, "", "", 1):eq("bad argument #3 (expected boolean, got number)")
expect.error(fs.complete, "", "", true, 1):eq("bad argument #4 (expected boolean, got number)")
end)
end)
describe("fs.isDriveRoot", function()
it("validates arguments", function()
fs.isDriveRoot("")
expect.error(fs.isDriveRoot, nil):eq("bad argument #1 (expected string, got nil)")
end)
it("correctly identifies drive roots", function()
expect(fs.isDriveRoot("/rom")):eq(true)
expect(fs.isDriveRoot("/")):eq(true)
expect(fs.isDriveRoot("/rom/startup.lua")):eq(false)
expect(fs.isDriveRoot("/rom/programs/delete.lua")):eq(false)
end)
end)
describe("fs.list", function()
it("fails on files", function()
expect.error(fs.list, "rom/startup.lua"):eq("/rom/startup.lua: Not a directory")
expect.error(fs.list, "startup.lua"):eq("/startup.lua: Not a directory")
end)
it("fails on non-existent nodes", function()
expect.error(fs.list, "rom/x"):eq("/rom/x: Not a directory")
expect.error(fs.list, "x"):eq("/x: Not a directory")
end)
end)
describe("fs.combine", function()
it("removes . and ..", function()
expect(fs.combine("./a/b")):eq("a/b")
expect(fs.combine("a/b", "../c")):eq("a/c")
expect(fs.combine("a", "../c")):eq("c")
expect(fs.combine("a", "../../c")):eq("../c")
end)
it("combines empty paths", function()
expect(fs.combine("a")):eq("a")
expect(fs.combine("a", "")):eq("a")
expect(fs.combine("", "a")):eq("a")
expect(fs.combine("a", "", "b", "c")):eq("a/b/c")
end)
end)
describe("fs.getSize", function()
it("fails on non-existent nodes", function()
expect.error(fs.getSize, "rom/x"):eq("/rom/x: No such file")
expect.error(fs.getSize, "x"):eq("/x: No such file")
end)
end)
describe("fs.open", function()
describe("reading", function()
it("fails on directories", function()
expect { fs.open("rom", "r") }:same { nil, "/rom: No such file" }
expect { fs.open("", "r") }:same { nil, "/: No such file" }
end)
it("fails on non-existent nodes", function()
expect { fs.open("rom/x", "r") }:same { nil, "/rom/x: No such file" }
expect { fs.open("x", "r") }:same { nil, "/x: No such file" }
end)
it("errors when closing twice", function()
local handle = fs.open("rom/startup.lua", "r")
handle.close()
expect.error(handle.close):eq("attempt to use a closed file")
end)
end)
describe("reading in binary mode", function()
it("errors when closing twice", function()
local handle = fs.open("rom/startup.lua", "rb")
handle.close()
expect.error(handle.close):eq("attempt to use a closed file")
end)
end)
describe("writing", function()
it("fails on directories", function()
expect { fs.open("", "w") }:same { nil, "/: Cannot write to directory" }
end)
it("fails on read-only mounts", function()
expect { fs.open("rom/x", "w") }:same { nil, "/rom/x: Access denied" }
end)
it("errors when closing twice", function()
local handle = fs.open("test-files/out.txt", "w")
handle.close()
expect.error(handle.close):eq("attempt to use a closed file")
end)
it("fails gracefully when opening 'CON' on Windows", function()
local ok, err = fs.open("test-files/con", "w")
if ok then fs.delete("test-files/con") return end
-- On my Windows/Java version the message appears to be "Incorrect function.". It may not be
-- consistent though, and honestly doesn't matter too much.
expect(err):str_match("^/test%-files/con: .*")
end)
end)
describe("writing in binary mode", function()
it("errors when closing twice", function()
local handle = fs.open("test-files/out.txt", "wb")
handle.close()
expect.error(handle.close):eq("attempt to use a closed file")
end)
end)
describe("appending", function()
it("fails on directories", function()
expect { fs.open("", "a") }:same { nil, "/: Cannot write to directory" }
end)
it("fails on read-only mounts", function()
expect { fs.open("rom/x", "a") }:same { nil, "/rom/x: Access denied" }
end)
end)
end)
describe("fs.makeDir", function()
it("fails on files", function()
expect.error(fs.makeDir, "startup.lua"):eq("/startup.lua: File exists")
end)
it("fails on read-only mounts", function()
expect.error(fs.makeDir, "rom/x"):eq("/rom/x: Access denied")
end)
end)
describe("fs.delete", function()
it("fails on read-only mounts", function()
expect.error(fs.delete, "rom/x"):eq("/rom/x: Access denied")
end)
end)
describe("fs.copy", function()
it("fails on read-only mounts", function()
expect.error(fs.copy, "rom", "rom/startup"):eq("/rom/startup: Access denied")
end)
it("fails to copy a folder inside itself", function()
fs.makeDir("some-folder")
expect.error(fs.copy, "some-folder", "some-folder/x"):eq("/some-folder: Can't copy a directory inside itself")
expect.error(fs.copy, "some-folder", "Some-Folder/x"):eq("/some-folder: Can't copy a directory inside itself")
end)
it("copies folders", function()
fs.delete("some-folder")
fs.delete("another-folder")
fs.makeDir("some-folder")
fs.copy("some-folder", "another-folder")
expect(fs.isDir("another-folder")):eq(true)
end)
end)
describe("fs.move", function()
it("fails on read-only mounts", function()
expect.error(fs.move, "rom", "rom/move"):eq("Access denied")
expect.error(fs.move, "test-files", "rom/move"):eq("Access denied")
expect.error(fs.move, "rom", "test-files"):eq("Access denied")
end)
end)
describe("fs.getCapacity", function()
it("returns nil on read-only mounts", function()
expect(fs.getCapacity("rom")):eq(nil)
end)
it("returns the capacity on the root mount", function()
expect(fs.getCapacity("")):eq(10000000)
end)
end)
describe("fs.attributes", function()
it("errors on non-existent files", function()
expect.error(fs.attributes, "xuxu_nao_existe"):eq("/xuxu_nao_existe: No such file")
end)
it("returns information about read-only mounts", function()
expect(fs.attributes("rom")):matches { isDir = true, size = 0, isReadOnly = true }
end)
it("returns information about files", function()
local now = os.epoch("utc")
fs.delete("/tmp/basic-file")
local h = fs.open("/tmp/basic-file", "w")
h.write("A reasonably sized string")
h.close()
local attributes = fs.attributes("tmp/basic-file")
expect(attributes):matches { isDir = false, size = 25, isReadOnly = false }
if attributes.created - now >= 1000 then
fail(("Expected created time (%d) to be within 1000ms of now (%d"):format(attributes.created, now))
end
if attributes.modified - now >= 1000 then
fail(("Expected modified time (%d) to be within 1000ms of now (%d"):format(attributes.modified, now))
end
expect(attributes.modification):eq(attributes.modified)
end)
end)
end)

View File

@ -1,15 +0,0 @@
describe("The gps library", function()
describe("gps.locate", function()
it("validates arguments", function()
stub(_G, "commands", { getBlockPosition = function()
end })
gps.locate()
gps.locate(1)
gps.locate(1, true)
expect.error(gps.locate, ""):eq("bad argument #1 (expected number, got string)")
expect.error(gps.locate, 1, ""):eq("bad argument #2 (expected boolean, got string)")
end)
end)
end)

View File

@ -1,22 +0,0 @@
describe("The help library", function()
describe("help.setPath", function()
it("validates arguments", function()
help.setPath(help.path())
expect.error(help.setPath, nil):eq("bad argument #1 (expected string, got nil)")
end)
end)
describe("help.lookup", function()
it("validates arguments", function()
help.lookup("")
expect.error(help.lookup, nil):eq("bad argument #1 (expected string, got nil)")
end)
end)
describe("help.completeTopic", function()
it("validates arguments", function()
help.completeTopic("")
expect.error(help.completeTopic, nil):eq("bad argument #1 (expected string, got nil)")
end)
end)
end)

View File

@ -1,19 +0,0 @@
describe("The http library", function()
describe("http.checkURL", function()
it("accepts well formed domains", function()
expect({ http.checkURL("https://google.com")}):same({ true })
end)
it("rejects malformed URLs", function()
expect({ http.checkURL("google.com")}):same({ false, "Must specify http or https" })
expect({ http.checkURL("https:google.com")}):same({ false, "URL malformed" })
expect({ http.checkURL("https:/google.com")}):same({ false, "URL malformed" })
expect({ http.checkURL("wss://google.com")}):same({ false, "Invalid protocol 'wss'" })
end)
it("rejects local domains", function()
expect({ http.checkURL("http://localhost")}):same({ false, "Domain not permitted" })
expect({ http.checkURL("http://127.0.0.1")}):same({ false, "Domain not permitted" })
end)
end)
end)

View File

@ -1,76 +0,0 @@
--- Tests the io library is (mostly) consistent with PUC Lua.
--
-- These tests are based on the tests for Lua 5.1
describe("The io library", function()
it("io.input on a handle returns that handle", function()
expect(io.input(io.stdin)):equals(io.stdin)
end)
it("io.output on a handle returns that handle", function()
expect(io.output(io.stdout)):equals(io.stdout)
end)
describe("io.type", function()
it("returns file on handles", function()
local handle = io.input()
expect(handle):type("table")
expect(io.type(handle)):equals("file")
end)
it("returns nil on values", function()
expect(io.type(8)):equals(nil)
end)
it("returns nil on tables", function()
expect(io.type(setmetatable({}, {}))):equals(nil)
end)
end)
describe("io.lines", function()
it("validates arguments", function()
io.lines(nil)
expect.error(io.lines, ""):eq("/: No such file")
expect.error(io.lines, false):eq("bad argument #1 (expected string, got boolean)")
end)
end)
describe("io.open", function()
it("validates arguments", function()
io.open("")
io.open("", "r")
expect.error(io.open, nil):eq("bad argument #1 (expected string, got nil)")
expect.error(io.open, "", false):eq("bad argument #2 (expected string, got boolean)")
end)
it("returns an error message on non-existent files", function()
local a, b = io.open('xuxu_nao_existe')
expect(a):equals(nil)
expect(b):type("string")
end)
end)
pending("io.output allows redirecting and seeking", function()
fs.delete("/tmp/io_spec.txt")
io.output("/tmp/io_spec.txt")
expect(io.output()):not_equals(io.stdout)
expect(io.output():seek()):equal(0)
assert(io.write("alo alo"))
expect(io.output():seek()):equal(#("alo alo"))
expect(io.output():seek("cur", -3)):equal(#("alo alo") - 3)
assert(io.write("joao"))
expect(io.output():seek("end"):equal(#("alo joao")))
expect(io.output():seek("set")):equal(0)
assert(io.write('"<22>lo"', "{a}\n", "second line\n", "third line \n"))
assert(io.write('<EFBFBD>fourth_line'))
io.output(io.stdout)
expect(io.output()):equals(io.stdout)
end)
end)

View File

@ -1,8 +0,0 @@
describe("The keys library", function()
describe("keys.getName", function()
it("validates arguments", function()
keys.getName(1)
expect.error(keys.getName, nil):eq("bad argument #1 (expected number, got nil)")
end)
end)
end)

View File

@ -1,133 +0,0 @@
describe("The os library", function()
describe("os.date and os.time", function()
it("round trips correctly", function()
local t = math.floor(os.epoch("local") / 1000)
local T = os.date("*t", t)
expect(os.time(T)):eq(t)
end)
it("dst field is guessed", function()
local T = os.date("*t")
local t = os.time(T)
expect(T.isdst):type("boolean")
T.isdst = nil
expect(os.time(T)):eq(t) -- if isdst is absent uses correct default
end)
it("has 365 days in a year", function()
local T = os.date("*t")
local t = os.time(T)
T.year = T.year - 1
local t1 = os.time(T)
local delta = (t - t1) / (24 * 3600) - 365
-- allow for leap years
assert(math.abs(delta) < 2, ("expected abs(%d )< 2"):format(delta))
end)
it("os.date uses local timezone", function()
local epoch = os.epoch("local") / 1000
local date = os.time(os.date("*t"))
assert(date - epoch <= 2, ("expected %d - %d <= 2, but not the case"):format(date, epoch))
end)
end)
describe("os.date", function()
it("formats as expected", function()
-- From the PUC Lua tests, hence the weird style
local t = os.epoch("local")
local T = os.date("*t", t)
_G.T = T
loadstring(os.date([[assert(T.year==%Y and T.month==%m and T.day==%d and
T.hour==%H and T.min==%M and T.sec==%S and
T.wday==%w+1 and T.yday==%j and type(T.isdst) == 'boolean')]], t))()
T = os.date("!*t", t)
_G.T = T
loadstring(os.date([[!assert(T.year==%Y and T.month==%m and T.day==%d and
T.hour==%H and T.min==%M and T.sec==%S and
T.wday==%w+1 and T.yday==%j and type(T.isdst) == 'boolean')]], t))()
end)
describe("produces output consistent with PUC Lua", function()
-- Create a separate test for each code, just so it's easier to see what's broken
local t1 = os.time { year = 2000, month = 10, day = 1, hour = 23, min = 12, sec = 17 }
local function exp_code(code, value)
it(("for code '%s'"):format(code), function()
expect(os.date(code, t1)):eq(value)
end)
end
exp_code("%a", "Sun")
exp_code("%A", "Sunday")
exp_code("%b", "Oct")
exp_code("%B", "October")
exp_code("%c", "Sun Oct 1 23:12:17 2000")
exp_code("%C", "20")
exp_code("%d", "01")
exp_code("%D", "10/01/00")
exp_code("%e", " 1")
exp_code("%F", "2000-10-01")
exp_code("%g", "00")
exp_code("%G", "2000")
exp_code("%h", "Oct")
exp_code("%H", "23")
exp_code("%I", "11")
exp_code("%j", "275")
exp_code("%m", "10")
exp_code("%M", "12")
exp_code("%n", "\n")
exp_code("%p", "PM")
exp_code("%r", "11:12:17 PM")
exp_code("%R", "23:12")
exp_code("%S", "17")
exp_code("%t", "\t")
exp_code("%T", "23:12:17")
exp_code("%u", "7")
exp_code("%U", "40")
exp_code("%V", "39")
exp_code("%w", "0")
exp_code("%W", "39")
exp_code("%x", "10/01/00")
exp_code("%X", "23:12:17")
exp_code("%y", "00")
exp_code("%Y", "2000")
exp_code("%%", "%")
it("zones are numbers", function()
local zone = os.date("%z", t1)
if not zone:match("^[+-]%d%d%d%d$") then
error("Invalid zone: " .. zone)
end
end)
it("zones id is made of letters", function()
local zone = os.date("%Z", t1)
if not zone:match("^%a%a+$") then
error("Non letter character in zone: " .. zone)
end
end)
end)
end)
describe("os.time", function()
it("maps directly to seconds", function()
local t1 = os.time { year = 2000, month = 10, day = 1, hour = 23, min = 12, sec = 17 }
local t2 = os.time { year = 2000, month = 10, day = 1, hour = 23, min = 10, sec = 19 }
expect(t1 - t2):eq(60 * 2 - 2)
end)
end)
describe("os.loadAPI", function()
it("validates arguments", function()
expect.error(os.loadAPI, nil):eq("bad argument #1 (expected string, got nil)")
end)
end)
describe("os.unloadAPI", function()
it("validates arguments", function()
expect.error(os.loadAPI, nil):eq("bad argument #1 (expected string, got nil)")
end)
end)
end)

View File

@ -1,176 +0,0 @@
local with_window = require "test_helpers".with_window
describe("The paintutils library", function()
-- Verifies that a window's lines are equal to the given table of blit
-- strings ({{"text", "fg", "bg"}, {"text", "fg", "bg"}...})
local function window_eq(w, state)
-- Verification of the size isn't really important in the tests, but
-- better safe than sorry.
local _, height = w.getSize()
expect(#state):eq(height)
for line = 1, height do
expect({ w.getLine(line) }):same(state[line])
end
end
describe("paintutils.parseImage", function()
it("validates arguments", function()
paintutils.parseImage("")
expect.error(paintutils.parseImage, nil):eq("bad argument #1 (expected string, got nil)")
end)
end)
describe("paintutils.loadImage", function()
it("validates arguments", function()
expect.error(paintutils.loadImage, nil):eq("bad argument #1 (expected string, got nil)")
end)
end)
describe("paintutils.drawPixel", function()
it("validates arguments", function()
expect.error(paintutils.drawPixel, nil):eq("bad argument #1 (expected number, got nil)")
expect.error(paintutils.drawPixel, 1, nil):eq("bad argument #2 (expected number, got nil)")
expect.error(paintutils.drawPixel, 1, 1, false):eq("bad argument #3 (expected number, got boolean)")
end)
end)
describe("paintutils.drawLine", function()
it("validates arguments", function()
expect.error(paintutils.drawLine, nil):eq("bad argument #1 (expected number, got nil)")
expect.error(paintutils.drawLine, 1, nil):eq("bad argument #2 (expected number, got nil)")
expect.error(paintutils.drawLine, 1, 1, nil):eq("bad argument #3 (expected number, got nil)")
expect.error(paintutils.drawLine, 1, 1, 1, nil):eq("bad argument #4 (expected number, got nil)")
expect.error(paintutils.drawLine, 1, 1, 1, 1, false):eq("bad argument #5 (expected number, got boolean)")
end)
it("draws a line going across with custom colour", function()
local w = with_window(3, 2, function()
paintutils.drawLine(1, 1, 3, 1, colours.red)
end)
window_eq(w, {
{ " ", "000", "eee" },
{ " ", "000", "fff" },
})
end)
it("draws a line going diagonally with term colour", function()
local w = with_window(3, 3, function()
term.setBackgroundColour(colours.red)
paintutils.drawLine(1, 1, 3, 3)
end)
window_eq(w, {
{ " ", "000", "eff" },
{ " ", "000", "fef" },
{ " ", "000", "ffe" },
})
end)
it("draws a line going diagonally from bottom left", function()
local w = with_window(3, 3, function()
term.setBackgroundColour(colours.red)
paintutils.drawLine(1, 3, 3, 1)
end)
window_eq(w, {
{ " ", "000", "ffe" },
{ " ", "000", "fef" },
{ " ", "000", "eff" },
})
end)
end)
describe("paintutils.drawBox", function()
it("validates arguments", function()
expect.error(paintutils.drawBox, nil):eq("bad argument #1 (expected number, got nil)")
expect.error(paintutils.drawBox, 1, nil):eq("bad argument #2 (expected number, got nil)")
expect.error(paintutils.drawBox, 1, 1, nil):eq("bad argument #3 (expected number, got nil)")
expect.error(paintutils.drawBox, 1, 1, 1, nil):eq("bad argument #4 (expected number, got nil)")
expect.error(paintutils.drawBox, 1, 1, 1, 1, false):eq("bad argument #5 (expected number, got boolean)")
end)
it("draws a box with term colour", function()
local w = with_window(3, 3, function()
term.setBackgroundColour(colours.red)
paintutils.drawBox(1, 1, 3, 3)
end)
window_eq(w, {
{ " ", "eee", "eee" },
{ " ", "e0e", "efe" },
{ " ", "eee", "eee" },
})
end)
it("draws a box with custom colour", function()
local w = with_window(3, 3, function()
paintutils.drawBox(1, 1, 3, 3, colours.red)
end)
window_eq(w, {
{ " ", "eee", "eee" },
{ " ", "e0e", "efe" },
{ " ", "eee", "eee" },
})
end)
it("draws a box without overwriting existing content", function()
local w = with_window(3, 3, function()
term.setCursorPos(2, 2)
term.write("a")
paintutils.drawBox(1, 1, 3, 3, colours.red)
end)
window_eq(w, {
{ " ", "eee", "eee" },
{ " a ", "e0e", "efe" },
{ " ", "eee", "eee" },
})
end)
end)
describe("paintutils.drawFilledBox", function()
it("validates arguments", function()
expect.error(paintutils.drawFilledBox, nil):eq("bad argument #1 (expected number, got nil)")
expect.error(paintutils.drawFilledBox, 1, nil):eq("bad argument #2 (expected number, got nil)")
expect.error(paintutils.drawFilledBox, 1, 1, nil):eq("bad argument #3 (expected number, got nil)")
expect.error(paintutils.drawFilledBox, 1, 1, 1, nil):eq("bad argument #4 (expected number, got nil)")
expect.error(paintutils.drawFilledBox, 1, 1, 1, 1, false):eq("bad argument #5 (expected number, got boolean)")
end)
it("draws a filled box with term colour", function()
local w = with_window(3, 3, function()
term.setBackgroundColour(colours.red)
paintutils.drawFilledBox(1, 1, 3, 3)
end)
window_eq(w, {
{ " ", "eee", "eee" },
{ " ", "eee", "eee" },
{ " ", "eee", "eee" },
})
end)
it("draws a filled box with custom colour", function()
local w = with_window(3, 3, function()
paintutils.drawFilledBox(1, 1, 3, 3, colours.red)
end)
window_eq(w, {
{ " ", "eee", "eee" },
{ " ", "eee", "eee" },
{ " ", "eee", "eee" },
})
end)
end)
describe("paintutils.drawImage", function()
it("validates arguments", function()
expect.error(paintutils.drawImage, nil):eq("bad argument #1 (expected table, got nil)")
expect.error(paintutils.drawImage, {}, nil):eq("bad argument #2 (expected number, got nil)")
expect.error(paintutils.drawImage, {}, 1, nil):eq("bad argument #3 (expected number, got nil)")
end)
end)
end)

View File

@ -1,47 +0,0 @@
describe("The peripheral library", function()
describe("peripheral.isPresent", function()
it("validates arguments", function()
peripheral.isPresent("")
expect.error(peripheral.isPresent, nil):eq("bad argument #1 (expected string, got nil)")
end)
end)
describe("peripheral.getType", function()
it("validates arguments", function()
peripheral.getType("")
expect.error(peripheral.getType, nil):eq("bad argument #1 (expected string, got nil)")
end)
end)
describe("peripheral.getMethods", function()
it("validates arguments", function()
peripheral.getMethods("")
expect.error(peripheral.getMethods, nil):eq("bad argument #1 (expected string, got nil)")
end)
end)
describe("peripheral.call", function()
it("validates arguments", function()
peripheral.call("", "")
expect.error(peripheral.call, nil):eq("bad argument #1 (expected string, got nil)")
expect.error(peripheral.call, "", nil):eq("bad argument #2 (expected string, got nil)")
end)
end)
describe("peripheral.wrap", function()
it("validates arguments", function()
peripheral.wrap("")
expect.error(peripheral.wrap, nil):eq("bad argument #1 (expected string, got nil)")
end)
end)
describe("peripheral.find", function()
it("validates arguments", function()
peripheral.find("")
peripheral.find("", function()
end)
expect.error(peripheral.find, nil):eq("bad argument #1 (expected string, got nil)")
expect.error(peripheral.find, "", false):eq("bad argument #2 (expected function, got boolean)")
end)
end)
end)

View File

@ -1,86 +0,0 @@
describe("The rednet library", function()
describe("rednet.open", function()
it("validates arguments", function()
expect.error(rednet.open, nil):eq("bad argument #1 (expected string, got nil)")
end)
it("requires a modem to be present", function()
expect.error(rednet.open, "not_there"):eq("No such modem: not_there")
end)
end)
describe("rednet.close", function()
it("validates arguments", function()
rednet.close()
expect.error(rednet.close, 1):eq("bad argument #1 (expected string, got number)")
expect.error(rednet.close, false):eq("bad argument #1 (expected string, got boolean)")
end)
it("requires a modem to be present", function()
expect.error(rednet.close, "not_there"):eq("No such modem: not_there")
end)
end)
describe("rednet.isOpen", function()
it("validates arguments", function()
rednet.isOpen()
rednet.isOpen("")
expect.error(rednet.isOpen, 1):eq("bad argument #1 (expected string, got number)")
expect.error(rednet.isOpen, false):eq("bad argument #1 (expected string, got boolean)")
end)
end)
describe("rednet.send", function()
it("validates arguments", function()
rednet.send(1)
rednet.send(1, nil, "")
expect.error(rednet.send, nil):eq("bad argument #1 (expected number, got nil)")
expect.error(rednet.send, 1, nil, false):eq("bad argument #3 (expected string, got boolean)")
end)
end)
describe("rednet.broadcast", function()
it("validates arguments", function()
rednet.broadcast(nil)
rednet.broadcast(nil, "")
expect.error(rednet.broadcast, nil, false):eq("bad argument #2 (expected string, got boolean)")
end)
end)
describe("rednet.receive", function()
it("validates arguments", function()
expect.error(rednet.receive, false):eq("bad argument #1 (expected string, got boolean)")
expect.error(rednet.receive, "", false):eq("bad argument #2 (expected number, got boolean)")
end)
end)
describe("rednet.host", function()
it("validates arguments", function()
expect.error(rednet.host, "", "localhost"):eq("Reserved hostname")
expect.error(rednet.host, nil):eq("bad argument #1 (expected string, got nil)")
expect.error(rednet.host, "", nil):eq("bad argument #2 (expected string, got nil)")
end)
end)
describe("rednet.unhost", function()
it("validates arguments", function()
rednet.unhost("")
expect.error(rednet.unhost, nil):eq("bad argument #1 (expected string, got nil)")
end)
end)
describe("rednet.lookup", function()
it("validates arguments", function()
expect.error(rednet.lookup, nil):eq("bad argument #1 (expected string, got nil)")
expect.error(rednet.lookup, "", false):eq("bad argument #2 (expected string, got boolean)")
end)
it("gets a locally hosted protocol", function()
rednet.host("a_protocol", "a_hostname")
expect(rednet.lookup("a_protocol")):eq(os.getComputerID())
expect(rednet.lookup("a_protocol", "localhost")):eq(os.getComputerID())
expect(rednet.lookup("a_protocol", "a_hostname")):eq(os.getComputerID())
end)
end)
end)

View File

@ -1,43 +0,0 @@
describe("The settings library", function()
describe("settings.set", function()
it("validates arguments", function()
settings.set("test", 1)
settings.set("test", "")
settings.set("test", {})
settings.set("test", false)
expect.error(settings.set, nil):eq("bad argument #1 (expected string, got nil)")
expect.error(settings.set, "", nil):eq("bad argument #2 (expected number, string, boolean or table, got nil)")
end)
it("prevents storing unserialisable types", function()
expect.error(settings.set, "", { print }):eq("Cannot serialize type function")
end)
end)
describe("settings.get", function()
it("validates arguments", function()
settings.get("test")
expect.error(settings.get, nil):eq("bad argument #1 (expected string, got nil)")
end)
end)
describe("settings.unset", function()
it("validates arguments", function()
settings.unset("test")
expect.error(settings.unset, nil):eq("bad argument #1 (expected string, got nil)")
end)
end)
describe("settings.load", function()
it("validates arguments", function()
expect.error(settings.load, nil):eq("bad argument #1 (expected string, got nil)")
end)
end)
describe("settings.save", function()
it("validates arguments", function()
expect.error(settings.save, nil):eq("bad argument #1 (expected string, got nil)")
end)
end)
end)

View File

@ -1,12 +0,0 @@
describe("The term library", function()
describe("term.redirect", function()
it("validates arguments", function()
expect.error(term.redirect, nil):eq("bad argument #1 (expected table, got nil)")
end)
it("prevents redirecting to term", function()
expect.error(term.redirect, term)
:eq("term is not a recommended redirect target, try term.current() instead")
end)
end)
end)

View File

@ -1,189 +0,0 @@
describe("The textutils library", function()
describe("textutils.slowWrite", function()
it("validates arguments", function()
expect.error(textutils.slowWrite, nil, false):eq("bad argument #2 (expected number, got boolean)")
end)
end)
describe("textutils.formatTime", function()
it("validates arguments", function()
textutils.formatTime(0)
textutils.formatTime(0, false)
expect.error(textutils.formatTime, nil):eq("bad argument #1 (expected number, got nil)")
expect.error(textutils.formatTime, 1, 1):eq("bad argument #2 (expected boolean, got number)")
end)
end)
describe("textutils.pagedPrint", function()
it("validates arguments", function()
expect.error(textutils.pagedPrint, nil, false):eq("bad argument #2 (expected number, got boolean)")
end)
end)
describe("textutils.tabulate", function()
it("validates arguments", function()
term.redirect(window.create(term.current(), 1, 1, 5, 5, false))
textutils.tabulate()
textutils.tabulate({ "test" })
textutils.tabulate(colors.white)
expect.error(textutils.tabulate, nil):eq("bad argument #1 (expected number or table, got nil)")
expect.error(textutils.tabulate, { "test" }, nil):eq("bad argument #2 (expected number or table, got nil)")
end)
end)
describe("textutils.pagedTabulate", function()
it("validates arguments", function()
term.redirect(window.create(term.current(), 1, 1, 5, 5, false))
textutils.pagedTabulate()
textutils.pagedTabulate({ "test" })
textutils.pagedTabulate(colors.white)
expect.error(textutils.pagedTabulate, nil):eq("bad argument #1 (expected number or table, got nil)")
expect.error(textutils.pagedTabulate, { "test" }, nil):eq("bad argument #2 (expected number or table, got nil)")
end)
end)
describe("textutils.empty_json_array", function()
it("is immutable", function()
expect.error(function() textutils.empty_json_array[1] = true end)
:eq("textutils_spec.lua:51: attempt to mutate textutils.empty_json_array")
end)
end)
describe("textutils.unserialise", function()
it("validates arguments", function()
textutils.unserialise("")
expect.error(textutils.unserialise, nil):eq("bad argument #1 (expected string, got nil)")
end)
end)
describe("textutils.serialiseJSON", function()
it("validates arguments", function()
textutils.serialiseJSON("")
textutils.serialiseJSON(1)
textutils.serialiseJSON({})
textutils.serialiseJSON(false)
textutils.serialiseJSON("", true)
expect.error(textutils.serialiseJSON, nil):eq("bad argument #1 (expected table, string, number or boolean, got nil)")
expect.error(textutils.serialiseJSON, "", 1):eq("bad argument #2 (expected boolean, got number)")
end)
it("serializes empty arrays", function()
expect(textutils.serializeJSON(textutils.empty_json_array)):eq("[]")
end)
it("serializes null", function()
expect(textutils.serializeJSON(textutils.json_null)):eq("null")
end)
it("serializes strings", function()
expect(textutils.serializeJSON('a')):eq('"a"')
expect(textutils.serializeJSON('"')):eq('"\\""')
expect(textutils.serializeJSON('\\')):eq('"\\\\"')
expect(textutils.serializeJSON('/')):eq('"/"')
expect(textutils.serializeJSON('\b')):eq('"\\b"')
expect(textutils.serializeJSON('\n')):eq('"\\n"')
expect(textutils.serializeJSON(string.char(0))):eq('"\\u0000"')
expect(textutils.serializeJSON(string.char(0x0A))):eq('"\\n"')
expect(textutils.serializeJSON(string.char(0x1D))):eq('"\\u001D"')
expect(textutils.serializeJSON(string.char(0x81))):eq('"\\u0081"')
expect(textutils.serializeJSON(string.char(0xFF))):eq('"\\u00FF"')
end)
it("serializes arrays until the last index with content", function()
expect(textutils.serializeJSON({ 5, "test", nil, nil, 7 })):eq('[5,"test",null,null,7]')
expect(textutils.serializeJSON({ 5, "test", nil, nil, textutils.json_null })):eq('[5,"test",null,null,null]')
expect(textutils.serializeJSON({ nil, nil, nil, nil, "text" })):eq('[null,null,null,null,"text"]')
end)
end)
describe("textutils.unserializeJSON", function()
describe("parses", function()
it("a list of primitives", function()
expect(textutils.unserializeJSON('[1, true, false, "hello"]')):same { 1, true, false, "hello" }
end)
it("null when parse_null is true", function()
expect(textutils.unserializeJSON("null", { parse_null = true })):eq(textutils.json_null)
end)
it("null when parse_null is false", function()
expect(textutils.unserializeJSON("null", { parse_null = false })):eq(nil)
end)
it("an empty array", function()
expect(textutils.unserializeJSON("[]", { parse_null = false })):eq(textutils.empty_json_array)
end)
it("basic objects", function()
expect(textutils.unserializeJSON([[{ "a": 1, "b":2 }]])):same { a = 1, b = 2 }
end)
end)
describe("parses using NBT-style syntax", function()
local function exp(x)
local res, err = textutils.unserializeJSON(x, { nbt_style = true })
if not res then error(err, 2) end
return expect(res)
end
it("basic objects", function()
exp([[{ a: 1, b:2 }]]):same { a = 1, b = 2 }
end)
it("suffixed numbers", function()
exp("1b"):eq(1)
exp("1.1d"):eq(1.1)
end)
it("strings", function()
exp("'123'"):eq("123")
exp("\"123\""):eq("123")
end)
it("typed arrays", function()
exp("[B; 1, 2, 3]"):same { 1, 2, 3 }
exp("[B;]"):same {}
end)
end)
describe("passes nst/JSONTestSuite", function()
local search_path = "test-rom/data/json-parsing"
local skip = dofile(search_path .. "/skip.lua")
for _, file in pairs(fs.find(search_path .. "/*.json")) do
local name = fs.getName(file):sub(1, -6);
(skip[name] and pending or it)(name, function()
local h = io.open(file, "r")
local contents = h:read("*a")
h:close()
local res, err = textutils.unserializeJSON(contents)
local kind = fs.getName(file):sub(1, 1)
if kind == "n" then
expect(res):eq(nil)
elseif kind == "y" then
if err ~= nil then fail("Expected test to pass, but failed with " .. err) end
end
end)
end
end)
end)
describe("textutils.urlEncode", function()
it("validates arguments", function()
textutils.urlEncode("")
expect.error(textutils.urlEncode, nil):eq("bad argument #1 (expected string, got nil)")
end)
end)
describe("textutils.complete", function()
it("validates arguments", function()
textutils.complete("pri")
textutils.complete("pri", _G)
expect.error(textutils.complete, nil):eq("bad argument #1 (expected string, got nil)")
expect.error(textutils.complete, "", false):eq("bad argument #2 (expected table, got boolean)")
end)
end)
end)

View File

@ -1,173 +0,0 @@
describe("The window library", function()
local function mk()
return window.create(term.current(), 1, 1, 5, 5, false)
end
describe("window.create", function()
it("validates arguments", function()
local r = mk()
window.create(r, 1, 1, 5, 5)
window.create(r, 1, 1, 5, 5, false)
expect.error(window.create, nil):eq("bad argument #1 (expected table, got nil)")
expect.error(window.create, r, nil):eq("bad argument #2 (expected number, got nil)")
expect.error(window.create, r, 1, nil):eq("bad argument #3 (expected number, got nil)")
expect.error(window.create, r, 1, 1, nil):eq("bad argument #4 (expected number, got nil)")
expect.error(window.create, r, 1, 1, 1, nil):eq("bad argument #5 (expected number, got nil)")
expect.error(window.create, r, 1, 1, 1, 1, ""):eq("bad argument #6 (expected boolean, got string)")
end)
end)
describe("Window.blit", function()
it("validates arguments", function()
local w = mk()
w.blit("a", "a", "a")
expect.error(w.blit, nil):eq("bad argument #1 (expected string, got nil)")
expect.error(w.blit, "", nil):eq("bad argument #2 (expected string, got nil)")
expect.error(w.blit, "", "", nil):eq("bad argument #3 (expected string, got nil)")
expect.error(w.blit, "", "", "a"):eq("Arguments must be the same length")
end)
end)
describe("Window.setCursorPos", function()
it("validates arguments", function()
local w = mk()
w.setCursorPos(1, 1)
expect.error(w.setCursorPos, nil):eq("bad argument #1 (expected number, got nil)")
expect.error(w.setCursorPos, 1, nil):eq("bad argument #2 (expected number, got nil)")
end)
end)
describe("Window.setCursorBlink", function()
it("validates arguments", function()
local w = mk()
w.setCursorBlink(false)
expect.error(w.setCursorBlink, nil):eq("bad argument #1 (expected boolean, got nil)")
end)
end)
describe("Window.setTextColour", function()
it("validates arguments", function()
local w = mk()
w.setTextColour(colors.white)
expect.error(w.setTextColour, nil):eq("bad argument #1 (expected number, got nil)")
expect.error(w.setTextColour, -5):eq("Invalid color (got -5)")
end)
end)
describe("Window.setPaletteColour", function()
it("validates arguments", function()
local w = mk()
w.setPaletteColour(colors.white, 0, 0, 0)
w.setPaletteColour(colors.white, 0x000000)
expect.error(w.setPaletteColour, nil):eq("bad argument #1 (expected number, got nil)")
expect.error(w.setPaletteColour, -5):eq("Invalid color (got -5)")
expect.error(w.setPaletteColour, colors.white):eq("bad argument #2 (expected number, got nil)")
expect.error(w.setPaletteColour, colors.white, 1, false):eq("bad argument #3 (expected number, got boolean)")
expect.error(w.setPaletteColour, colors.white, 1, nil, 1):eq("bad argument #3 (expected number, got nil)")
expect.error(w.setPaletteColour, colors.white, 1, 1, nil):eq("bad argument #4 (expected number, got nil)")
end)
end)
describe("Window.getPaletteColour", function()
it("validates arguments", function()
local w = mk()
w.getPaletteColour(colors.white)
expect.error(w.getPaletteColour, nil):eq("bad argument #1 (expected number, got nil)")
expect.error(w.getPaletteColour, -5):eq("Invalid color (got -5)")
end)
end)
describe("Window.setBackgroundColour", function()
it("validates arguments", function()
local w = mk()
w.setBackgroundColour(colors.white)
expect.error(w.setBackgroundColour, nil):eq("bad argument #1 (expected number, got nil)")
expect.error(w.setBackgroundColour, -5):eq("Invalid color (got -5)")
end)
end)
describe("Window.scroll", function()
it("validates arguments", function()
local w = mk()
w.scroll(0)
expect.error(w.scroll, nil):eq("bad argument #1 (expected number, got nil)")
end)
end)
describe("Window.setVisible", function()
it("validates arguments", function()
local w = mk()
w.setVisible(false)
expect.error(w.setVisible, nil):eq("bad argument #1 (expected boolean, got nil)")
end)
end)
describe("Window.reposition", function()
it("validates arguments", function()
local w = mk()
w.reposition(1, 1)
w.reposition(1, 1, 5, 5)
expect.error(w.reposition, nil):eq("bad argument #1 (expected number, got nil)")
expect.error(w.reposition, 1, nil):eq("bad argument #2 (expected number, got nil)")
expect.error(w.reposition, 1, 1, false, 1):eq("bad argument #3 (expected number, got boolean)")
expect.error(w.reposition, 1, 1, nil, 1):eq("bad argument #3 (expected number, got nil)")
expect.error(w.reposition, 1, 1, 1, nil):eq("bad argument #4 (expected number, got nil)")
expect.error(w.reposition, 1, 1, 1, 1, true):eq("bad argument #5 (expected table, got boolean)")
end)
it("can change the buffer", function()
local a, b = mk(), mk()
local target = window.create(a, 1, 1, a.getSize())
target.write("Test")
expect((a.getLine(1))):equal("Test ")
expect({ a.getCursorPos() }):same { 5, 1 }
target.reposition(1, 1, nil, nil, b)
target.redraw()
expect((a.getLine(1))):equal("Test ")
expect({ a.getCursorPos() }):same { 5, 1 }
target.setCursorPos(1, 1) target.write("More")
expect((a.getLine(1))):equal("Test ")
expect((b.getLine(1))):equal("More ")
end)
end)
describe("Window.getLine", function()
it("validates arguments", function()
local w = mk()
w.getLine(1)
local _, y = w.getSize()
expect.error(w.getLine, nil):eq("bad argument #1 (expected number, got nil)")
expect.error(w.getLine, 0):eq("Line is out of range.")
expect.error(w.getLine, y + 1):eq("Line is out of range.")
end)
it("provides a line's contents", function()
local w = mk()
w.blit("test", "aaaa", "4444")
expect({ w.getLine(1) }):same { "test ", "aaaa0", "4444f" }
end)
end)
describe("Window.setVisible", function()
it("validates arguments", function()
local w = mk()
expect.error(w.setVisible, nil):eq("bad argument #1 (expected boolean, got nil)")
end)
end)
describe("Window.isVisible", function()
it("gets window visibility", function()
local w = mk()
w.setVisible(false)
expect(w.isVisible()):same(false)
end)
end)
end)

View File

@ -1,123 +0,0 @@
describe("The Lua base library", function()
describe("expect", function()
local e = _G["~expect"]
it("checks a single type", function()
expect(e(1, "test", "string")):eq(true)
expect(e(1, 2, "number")):eq(true)
expect.error(e, 1, nil, "string"):eq("bad argument #1 (expected string, got nil)")
expect.error(e, 2, 1, "nil"):eq("bad argument #2 (expected nil, got number)")
end)
it("checks multiple types", function()
expect(e(1, "test", "string", "number")):eq(true)
expect(e(1, 2, "string", "number")):eq(true)
expect.error(e, 1, nil, "string", "number"):eq("bad argument #1 (expected string or number, got nil)")
expect.error(e, 2, false, "string", "table", "number", "nil")
:eq("bad argument #2 (expected string, table or number, got boolean)")
end)
it("includes the function name", function()
local function worker()
expect(e(1, nil, "string")):eq(true)
end
local function trampoline()
worker()
end
expect.error(trampoline):eq("base_spec.lua:27: bad argument #1 to 'worker' (expected string, got nil)")
end)
end)
describe("sleep", function()
it("validates arguments", function()
sleep(0)
sleep(nil)
expect.error(sleep, false):eq("bad argument #1 (expected number, got boolean)")
end)
end)
describe("write", function()
it("validates arguments", function()
write("")
expect.error(write, nil):eq("bad argument #1 (expected string or number, got nil)")
end)
end)
describe("loadfile", function()
it("validates arguments", function()
loadfile("")
loadfile("", {})
expect.error(loadfile, nil):eq("bad argument #1 (expected string, got nil)")
expect.error(loadfile, "", false):eq("bad argument #2 (expected table, got boolean)")
end)
it("prefixes the filename with @", function()
local info = debug.getinfo(loadfile("/rom/startup.lua"), "S")
expect(info):matches { short_src = "startup.lua", source = "@startup.lua" }
end)
end)
describe("dofile", function()
it("validates arguments", function()
expect.error(dofile, ""):eq("File not found")
expect.error(dofile, nil):eq("bad argument #1 (expected string, got nil)")
end)
end)
describe("loadstring", function()
it("prefixes the chunk name with '='", function()
local info = debug.getinfo(loadstring("return 1", "name"), "S")
expect(info):matches { short_src = "name", source = "=name" }
end)
it("does not prefix for unnamed chunks", function()
local info = debug.getinfo(loadstring("return 1"), "S")
expect(info):matches { short_src = '[string "return 1"]', source = "return 1", }
end)
it("does not prefix when already prefixed", function()
local info = debug.getinfo(loadstring("return 1", "@file.lua"), "S")
expect(info):matches { short_src = "file.lua", source = "@file.lua" }
info = debug.getinfo(loadstring("return 1", "=file.lua"), "S")
expect(info):matches { short_src = "file.lua", source = "=file.lua" }
end)
end)
describe("load", function()
it("validates arguments", function()
load("")
load(function()
end)
load("", "")
load("", "", "")
load("", "", "", _ENV)
expect.error(load, nil):eq("bad argument #1 (expected function or string, got nil)")
expect.error(load, "", false):eq("bad argument #2 (expected string, got boolean)")
expect.error(load, "", "", false):eq("bad argument #3 (expected string, got boolean)")
expect.error(load, "", "", "", false):eq("bad argument #4 (expected table, got boolean)")
end)
local function generator(parts)
return coroutine.wrap(function()
for i = 1, #parts do
coroutine.yield(parts[i])
end
end)
end
it("does not prefix the chunk name with '='", function()
local info = debug.getinfo(load("return 1", "name"), "S")
expect(info):matches { short_src = "[string \"name\"]", source = "name" }
info = debug.getinfo(load(generator { "return 1" }, "name"), "S")
expect(info):matches { short_src = "[string \"name\"]", source = "name" }
end)
end)
end)

View File

@ -1,41 +0,0 @@
describe("cc.pretty", function()
local str = require("cc.strings")
describe("wrap", function()
it("validates arguments", function()
str.wrap("test string is long")
str.wrap("test string is long", 11)
expect.error(str.wrap, nil):eq("bad argument #1 (expected string, got nil)")
expect.error(str.wrap, "", false):eq("bad argument #2 (expected number, got boolean)")
end)
it("wraps lines", function()
expect(str.wrap("test string is long")[1]):eq("test string is long")
expect(str.wrap("test string is long", 15)[1]):eq("test string is ")
expect(str.wrap("test string is long", 15)[2]):eq("long")
expect(str.wrap("test string is long", 12)[1]):eq("test string ")
expect(str.wrap("test string is long", 12)[2]):eq("is long")
expect(str.wrap("test string is long", 11)[1]):eq("test string")
expect(str.wrap("test string is long", 11)[2]):eq("is long")
end)
end)
describe("ensure_width", function()
it("validates arguments", function()
str.wrap("test string is long")
str.wrap("test string is long", 11)
expect.error(str.ensure_width, nil):eq("bad argument #1 (expected string, got nil)")
expect.error(str.ensure_width, "", false):eq("bad argument #2 (expected number, got boolean)")
end)
it("pads lines", function()
expect(str.ensure_width("test string is long", 25)):eq("test string is long ")
end)
it("truncates lines", function()
expect(str.ensure_width("test string is long", 15)):eq("test string is ")
end)
end)
end)

View File

@ -1,30 +0,0 @@
describe("The multishell program", function()
describe("multishell.setFocus", function()
it("validates arguments", function()
multishell.setFocus(multishell.getFocus())
expect.error(multishell.setFocus, nil):eq("bad argument #1 (expected number, got nil)")
end)
end)
describe("multishell.getTitle", function()
it("validates arguments", function()
multishell.getTitle(1)
expect.error(multishell.getTitle, nil):eq("bad argument #1 (expected number, got nil)")
end)
end)
describe("multishell.setTitle", function()
it("validates arguments", function()
multishell.setTitle(1, multishell.getTitle(1))
expect.error(multishell.setTitle, nil):eq("bad argument #1 (expected number, got nil)")
expect.error(multishell.setTitle, 1, nil):eq("bad argument #2 (expected string, got nil)")
end)
end)
describe("multishell.launch", function()
it("validates arguments", function()
expect.error(multishell.launch, nil):eq("bad argument #1 (expected table, got nil)")
expect.error(multishell.launch, _ENV, nil):eq("bad argument #2 (expected string, got nil)")
end)
end)
end)

View File

@ -1,33 +0,0 @@
local capture = require "test_helpers".capture_program
describe("The exec program", function()
it("displays an error without the commands api", function()
stub(_G, "commands", nil)
expect(capture(stub, "/rom/programs/command/exec.lua"))
:matches { ok = true, output = "", error = "Requires a Command Computer.\n" }
end)
it("displays its usage when given no argument", function()
stub(_G, "commands", {})
expect(capture(stub, "/rom/programs/command/exec.lua"))
:matches { ok = true, output = "", error = "Usage: /rom/programs/command/exec.lua <command>\n" }
end)
it("runs a command", function()
stub(_G, "commands", {
exec = function() return true, { "Hello World!" } end,
})
expect(capture(stub, "/rom/programs/command/exec.lua computercraft"))
:matches { ok = true, output = "Success\nHello World!\n", error = "" }
end)
it("reports command failures", function()
stub(_G, "commands", {
exec = function() return false, { "Hello World!" } end,
})
expect(capture(stub, "/rom/programs/command/exec.lua computercraft"))
:matches { ok = true, output = "Hello World!\n", error = "Failed\n" }
end)
end)

View File

@ -1,40 +0,0 @@
local capture = require "test_helpers".capture_program
describe("The copy program", function()
local function touch(file)
io.open(file, "w"):close()
end
it("copies a file", function()
touch("/test-files/copy/a.txt")
shell.run("copy /test-files/copy/a.txt /test-files/copy/b.txt")
expect(fs.exists("/test-files/copy/a.txt")):eq(true)
expect(fs.exists("/test-files/copy/b.txt")):eq(true)
end)
it("fails when copying a non-existent file", function()
expect(capture(stub, "copy nothing destination"))
:matches { ok = true, output = "", error = "No matching files\n" }
end)
it("fails when overwriting an existing file", function()
touch("/test-files/copy/c.txt")
expect(capture(stub, "copy /test-files/copy/c.txt /test-files/copy/c.txt"))
:matches { ok = true, output = "", error = "Destination exists\n" }
end)
it("fails when copying into read-only locations", function()
touch("/test-files/copy/d.txt")
expect(capture(stub, "copy /test-files/copy/d.txt /rom/test.txt"))
:matches { ok = true, output = "", error = "Destination is read-only\n" }
end)
it("displays the usage when given no arguments", function()
expect(capture(stub, "copy"))
:matches { ok = true, output = "Usage: copy <source> <destination>\n", error = "" }
end)
end)

View File

@ -1,35 +0,0 @@
describe("The rm program", function()
local function touch(file)
io.open(file, "w"):close()
end
it("deletes one file", function()
touch("/test-files/a.txt")
shell.run("rm /test-files/a.txt")
expect(fs.exists("/test-files/a.txt")):eq(false)
end)
it("deletes many files", function()
touch("/test-files/a.txt")
touch("/test-files/b.txt")
touch("/test-files/c.txt")
shell.run("rm /test-files/a.txt /test-files/b.txt")
expect(fs.exists("/test-files/a.txt")):eq(false)
expect(fs.exists("/test-files/b.txt")):eq(false)
expect(fs.exists("/test-files/c.txt")):eq(true)
end)
it("deletes a glob", function()
touch("/test-files/a.txt")
touch("/test-files/b.txt")
shell.run("rm /test-files/*.txt")
expect(fs.exists("/test-files/a.txt")):eq(false)
expect(fs.exists("/test-files/b.txt")):eq(false)
end)
end)

View File

@ -1,29 +0,0 @@
describe("The mkdir program", function()
it("creates a directory", function()
fs.delete("/test-files")
shell.run("mkdir /test-files/a")
expect(fs.isDir("/test-files/a")):eq(true)
end)
it("creates many directories", function()
fs.delete("/test-files")
shell.run("mkdir /test-files/a /test-files/b")
expect(fs.isDir("/test-files/a")):eq(true)
expect(fs.isDir("/test-files/b")):eq(true)
end)
it("can be completed", function()
fs.delete("/test-files")
fs.makeDir("/test-files/a")
fs.makeDir("/test-files/b")
io.open("/test-files.a.txt", "w"):close()
local complete = shell.getCompletionInfo()["rom/programs/mkdir.lua"].fnComplete
expect(complete(shell, 1, "/test-files/", {})):same { "a/", "b/" }
expect(complete(shell, 2, "/test-files/", { "/" })):same { "a/", "b/" }
end)
end)

View File

@ -1,36 +0,0 @@
local capture = require "test_helpers".capture_program
describe("The motd program", function()
local function setup_date(month, day)
stub(os, "date", function() return { month = month, day = day } end)
end
it("displays MOTD", function()
setup_date(0, 0)
local file = fs.open("/motd_check.txt", "w")
file.write("Hello World!")
file.close()
settings.set("motd.path", "/motd_check.txt")
expect(capture(stub, "motd"))
:matches { ok = true, output = "Hello World!\n", error = "" }
end)
it("displays date-specific MOTD (1/1)", function()
setup_date(1, 1)
expect(capture(stub, "motd"))
:matches { ok = true, output = "Happy new year!\n", error = "" }
end)
it("displays date-specific MOTD (10/31)", function()
setup_date(10, 31)
expect(capture(stub, "motd"))
:matches { ok = true, output = "OOoooOOOoooo! Spooky!\n", error = "" }
end)
it("displays date-specific MOTD (12/24)", function()
setup_date(12, 24)
expect(capture(stub, "motd"))
:matches { ok = true, output = "Merry X-mas!\n", error = "" }
end)
end)

View File

@ -1,74 +0,0 @@
local capture = require "test_helpers".capture_program
describe("The move program", function()
local function cleanup() fs.delete("/test-files/move") end
local function touch(file)
io.open(file, "w"):close()
end
it("move a file", function()
cleanup()
touch("/test-files/move/a.txt")
shell.run("move /test-files/move/a.txt /test-files/move/b.txt")
expect(fs.exists("/test-files/move/a.txt")):eq(false)
expect(fs.exists("/test-files/move/b.txt")):eq(true)
end)
it("moves a file to a directory", function()
cleanup()
touch("/test-files/move/a.txt")
fs.makeDir("/test-files/move/a")
expect(capture(stub, "move /test-files/move/a.txt /test-files/move/a"))
:matches { ok = true }
expect(fs.exists("/test-files/move/a.txt")):eq(false)
expect(fs.exists("/test-files/move/a/a.txt")):eq(true)
end)
it("fails when moving a file which doesn't exist", function()
expect(capture(stub, "move nothing destination"))
:matches { ok = true, output = "", error = "No matching files\n" }
end)
it("fails when overwriting an existing file", function()
cleanup()
touch("/test-files/move/a.txt")
expect(capture(stub, "move /test-files/move/a.txt /test-files/move/a.txt"))
:matches { ok = true, output = "", error = "Destination exists\n" }
end)
it("fails when moving to read-only locations", function()
cleanup()
touch("/test-files/move/a.txt")
expect(capture(stub, "move /test-files/move/a.txt /rom/test.txt"))
:matches { ok = true, output = "", error = "Destination is read-only\n" }
end)
it("fails when moving from read-only locations", function()
expect(capture(stub, "move /rom/startup.lua /test-files/move/not-exist.txt"))
:matches { ok = true, output = "", error = "Cannot move read-only file /rom/startup.lua\n" }
end)
it("fails when moving mounts", function()
expect(capture(stub, "move /rom /test-files/move/rom"))
:matches { ok = true, output = "", error = "Cannot move mount /rom\n" }
end)
it("fails when moving a file multiple times", function()
cleanup()
touch("/test-files/move/a.txt")
touch("/test-files/move/b.txt")
expect(capture(stub, "move /test-files/move/*.txt /test-files/move/c.txt"))
:matches { ok = true, output = "", error = "Cannot overwrite file multiple times\n" }
end)
it("displays the usage with no arguments", function()
expect(capture(stub, "move"))
:matches { ok = true, output = "Usage: move <source> <destination>\n", error = "" }
end)
end)

View File

@ -1,94 +0,0 @@
describe("The shell", function()
describe("require", function()
it("validates arguments", function()
require("math")
expect.error(require, nil):eq("bad argument #1 (expected string, got nil)")
end)
end)
describe("shell.run", function()
it("sets the arguments", function()
local handle = fs.open("test-files/out.txt", "w")
handle.writeLine("_G.__arg = arg")
handle.close()
shell.run("/test-files/out.txt", "arg1", "arg2")
fs.delete("test-files/out.txt")
local args = _G.__arg
_G.__arg = nil
expect(args):same { [0] = "/test-files/out.txt", "arg1", "arg2" }
end)
end)
describe("shell.setDir", function()
it("validates arguments", function()
shell.setDir(shell.dir())
expect.error(shell.setDir, nil):eq("bad argument #1 (expected string, got nil)")
end)
end)
describe("shell.setPath", function()
it("validates arguments", function()
shell.setPath(shell.path())
expect.error(shell.setPath, nil):eq("bad argument #1 (expected string, got nil)")
end)
end)
describe("shell.resolve", function()
it("validates arguments", function()
shell.resolve("")
expect.error(shell.resolve, nil):eq("bad argument #1 (expected string, got nil)")
end)
end)
describe("shell.resolveProgram", function()
it("validates arguments", function()
shell.resolveProgram("ls")
expect.error(shell.resolveProgram, nil):eq("bad argument #1 (expected string, got nil)")
end)
end)
describe("shell.complete", function()
it("validates arguments", function()
shell.complete("ls")
expect.error(shell.complete, nil):eq("bad argument #1 (expected string, got nil)")
end)
end)
describe("shell.setCompletionFunction", function()
it("validates arguments", function()
expect.error(shell.setCompletionFunction, nil):eq("bad argument #1 (expected string, got nil)")
expect.error(shell.setCompletionFunction, "", nil):eq("bad argument #2 (expected function, got nil)")
end)
end)
describe("shell.setCompletionFunction", function()
it("validates arguments", function()
expect.error(shell.setCompletionFunction, nil):eq("bad argument #1 (expected string, got nil)")
expect.error(shell.setCompletionFunction, "", nil):eq("bad argument #2 (expected function, got nil)")
end)
end)
describe("shell.setAlias", function()
it("validates arguments", function()
shell.setAlias("sl", "ls")
expect.error(shell.setAlias, nil):eq("bad argument #1 (expected string, got nil)")
expect.error(shell.setAlias, "", nil):eq("bad argument #2 (expected string, got nil)")
end)
end)
describe("shell.clearAlias", function()
it("validates arguments", function()
shell.clearAlias("sl")
expect.error(shell.clearAlias, nil):eq("bad argument #1 (expected string, got nil)")
end)
end)
describe("shell.switchTab", function()
it("validates arguments", function()
expect.error(shell.switchTab, nil):eq("bad argument #1 (expected number, got nil)")
end)
end)
end)

View File

@ -1,69 +0,0 @@
local capture = require "test_helpers".capture_program
describe("The craft program", function()
it("errors when not a turtle", function()
stub(_G, "turtle", nil)
expect(capture(stub, "/rom/programs/turtle/craft.lua"))
:matches { ok = true, output = "", error = "Requires a Turtle\n" }
end)
it("fails when turtle.craft() is unavailable", function()
stub(_G, "turtle", {})
expect(capture(stub, "/rom/programs/turtle/craft.lua"))
:matches { ok = true, output = "Requires a Crafty Turtle\n", error = "" }
end)
it("displays its usage when given no arguments", function()
stub(_G, "turtle", { craft = function() end })
expect(capture(stub, "/rom/programs/turtle/craft.lua"))
:matches { ok = true, output = "Usage: /rom/programs/turtle/craft.lua [number]\n", error = "" }
end)
it("crafts multiple items", function()
local item_count = 3
stub(_G, "turtle", {
craft = function()
item_count = 1
return true
end,
getItemCount = function() return item_count end,
getSelectedSlot = function() return 1 end,
})
expect(capture(stub, "/rom/programs/turtle/craft.lua 2"))
:matches { ok = true, output = "2 items crafted\n", error = "" }
end)
it("craft a single item", function()
local item_count = 2
stub(_G, "turtle", {
craft = function()
item_count = 1
return true
end,
getItemCount = function() return item_count end,
getSelectedSlot = function() return 1 end,
})
expect(capture(stub, "/rom/programs/turtle/craft.lua 1"))
:matches { ok = true, output = "1 item crafted\n", error = "" }
end)
it("crafts no items", function()
local item_count = 2
stub(_G, "turtle", {
craft = function()
item_count = 1
return false
end,
getItemCount = function() return item_count end,
getSelectedSlot = function() return 1 end,
})
expect(capture(stub, "/rom/programs/turtle/craft.lua 1"))
:matches { ok = true, output = "No items crafted\n", error = "" }
end)
end)

View File

@ -1,89 +0,0 @@
local capture = require "test_helpers".capture_program
describe("The turtle equip program", function()
it("errors when not a turtle", function()
stub(_G, "turtle", nil)
expect(capture(stub, "/rom/programs/turtle/equip.lua"))
:matches { ok = true, output = "", error = "Requires a Turtle\n" }
end)
it("displays its usage when given no arguments", function()
stub(_G, "turtle", {})
expect(capture(stub, "/rom/programs/turtle/equip.lua"))
:matches { ok = true, output = "Usage: /rom/programs/turtle/equip.lua <slot> <side>\n", error = "" }
end)
it("equip nothing", function()
stub(_G, "turtle", {
select = function() end,
getItemCount = function() return 0 end,
})
expect(capture(stub, "/rom/programs/turtle/equip.lua 1 left"))
:matches { ok = true, output = "Nothing to equip\n", error = "" }
expect(capture(stub, "/rom/programs/turtle/equip.lua 1 right"))
:matches { ok = true, output = "Nothing to equip\n", error = "" }
end)
it("swaps existing upgrades", function()
stub(_G, "turtle", {
select = function() end,
getItemCount = function() return 1 end,
equipLeft = function() return true end,
equipRight = function() return true end,
})
expect(capture(stub, "/rom/programs/turtle/equip.lua 1 left"))
:matches { ok = true, output = "Items swapped\n", error = "" }
expect(capture(stub, "/rom/programs/turtle/equip.lua 1 right"))
:matches { ok = true, output = "Items swapped\n", error = "" }
end)
describe("equips a new upgrade", function()
local function setup()
local item_count = 1
stub(_G, "turtle", {
select = function() end,
getItemCount = function() return item_count end,
equipLeft = function()
item_count = 0
return true
end,
equipRight = function()
item_count = 0
return true
end,
})
end
it("on the left", function()
setup()
expect(capture(stub, "/rom/programs/turtle/equip.lua 1 left"))
:matches { ok = true, output = "Item equipped\n", error = "" }
end)
it("on the right", function()
setup()
expect(capture(stub, "/rom/programs/turtle/equip.lua 1 right"))
:matches { ok = true, output = "Item equipped\n", error = "" }
end)
end)
it("handles when an upgrade cannot be equipped", function()
stub(_G, "turtle", {
select = function() end,
getItemCount = function() return 1 end,
equipLeft = function() return false end,
equipRight = function() return false end,
})
expect(capture(stub, "/rom/programs/turtle/equip.lua 1 left"))
:matches { ok = true, output = "Item not equippable\n", error = "" }
expect(capture(stub, "/rom/programs/turtle/equip.lua 1 right"))
:matches { ok = true, output = "Item not equippable\n", error = "" }
end)
end)

View File

@ -1,62 +0,0 @@
local capture = require "test_helpers".capture_program
describe("The refuel program", function()
local function setup_turtle(fuel_level, fuel_limit, item_count)
stub(_G, "turtle", {
getFuelLevel = function()
return fuel_level
end,
getItemCount = function()
return item_count
end,
refuel = function(nLimit)
item_count = item_count - nLimit
fuel_level = fuel_level + nLimit
end,
select = function()
end,
getFuelLimit = function()
return fuel_limit
end,
})
end
it("errors when not a turtle", function()
stub(_G, "turtle", nil)
expect(capture(stub, "/rom/programs/turtle/refuel.lua"))
:matches { ok = true, output = "", error = "Requires a Turtle\n" }
end)
it("displays its usage when given too many argument", function()
setup_turtle(0, 5, 0)
expect(capture(stub, "/rom/programs/turtle/refuel.lua a b"))
:matches { ok = true, output = "Usage: /rom/programs/turtle/refuel.lua [number]\n", error = "" }
end)
it("requires a numeric argument", function()
setup_turtle(0, 0, 0)
expect(capture(stub, "/rom/programs/turtle/refuel.lua nothing"))
:matches { ok = true, output = "Invalid limit, expected a number or \"all\"\n", error = "" }
end)
it("refuels the turtle", function()
setup_turtle(0, 10, 5)
expect(capture(stub, "/rom/programs/turtle/refuel.lua 5"))
:matches { ok = true, output = "Fuel level is 5\n", error = "" }
end)
it("reports when the fuel limit is reached", function()
setup_turtle(0, 5, 5)
expect(capture(stub, "/rom/programs/turtle/refuel.lua 5"))
:matches { ok = true, output = "Fuel level is 5\nFuel limit reached\n", error = "" }
end)
it("reports when the fuel level is unlimited", function()
setup_turtle("unlimited", 5, 5)
expect(capture(stub, "/rom/programs/turtle/refuel.lua 5"))
:matches { ok = true, output = "Fuel level is unlimited\n", error = "" }
end)
end)

View File

@ -1,69 +0,0 @@
local capture = require "test_helpers".capture_program
describe("The turtle unequip program", function()
it("errors when not a turtle", function()
stub(_G, "turtle", nil)
expect(capture(stub, "/rom/programs/turtle/unequip.lua"))
:matches { ok = true, output = "", error = "Requires a Turtle\n" }
end)
it("displays its usage when given no arguments", function()
stub(_G, "turtle", {})
expect(capture(stub, "/rom/programs/turtle/unequip.lua"))
:matches { ok = true, output = "Usage: /rom/programs/turtle/unequip.lua <side>\n", error = "" }
end)
it("says when nothing was unequipped", function()
stub(_G, "turtle", {
select = function() end,
getItemCount = function() return 0 end,
equipRight = function() return true end,
equipLeft = function() return true end,
})
expect(capture(stub, "/rom/programs/turtle/unequip.lua left"))
:matches { ok = true, output = "Nothing to unequip\n", error = "" }
expect(capture(stub, "/rom/programs/turtle/unequip.lua right"))
:matches { ok = true, output = "Nothing to unequip\n", error = "" }
end)
it("unequips a upgrade", function()
local item_count = 0
stub(_G, "turtle", {
select = function() end,
getItemCount = function() return item_count end,
equipRight = function()
item_count = 1
return true
end,
equipLeft = function()
item_count = 1
return true
end,
})
expect(capture(stub, "/rom/programs/turtle/unequip.lua left"))
:matches { ok = true, output = "Item unequipped\n", error = "" }
item_count = 0
expect(capture(stub, "/rom/programs/turtle/unequip.lua right"))
:matches { ok = true, output = "Item unequipped\n", error = "" }
end)
it("fails when the turtle is full", function()
stub(_G, "turtle", {
select = function() end,
getItemCount = function() return 1 end,
equipRight = function() return true end,
equipLeft = function() return true end,
})
expect(capture(stub, "/rom/programs/turtle/unequip.lua left"))
:matches { ok = true, output = "No space to unequip item\n", error = "" }
expect(capture(stub, "/rom/programs/turtle/unequip.lua right"))
:matches { ok = true, output = "No space to unequip item\n", error = "" }
end)
end)