1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2024-09-27 14:48:20 +00:00

Add a test which boots a computer and runs forever

Ideally we'd add a couple more tests in the future, but this'll do for
now.

The bootstrap class is largely yoinked from CCTweaks-Lua, so is a tad
ugly. It works though.
This commit is contained in:
SquidDev 2019-02-26 08:44:17 +00:00
parent f1d10809d5
commit c373583723
6 changed files with 484 additions and 3 deletions

View File

@ -186,7 +186,7 @@ public class Computer
}
}
public void advance()
public void tick()
{
synchronized( this )
{
@ -568,7 +568,7 @@ public class Computer
@SuppressWarnings( "unused" )
public void advance( double dt )
{
advance();
tick();
}
public static final String[] s_sideNames = IAPIEnvironment.SIDE_NAMES;

View File

@ -105,7 +105,7 @@ public class ServerComputer extends ServerTerminal implements IComputer, IComput
public void update()
{
super.update();
m_computer.advance();
m_computer.tick();
m_changedLastFrame = m_computer.pollAndResetChanged() || m_changed;
m_changed = false;

View File

@ -0,0 +1,155 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. 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 net.minecraftforge.fml.common.Loader;
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;
}
@Override
public String getHostString()
{
return "ComputerCraft ${version} (Minecraft " + Loader.MC_VERSION + ")";
}
@Override
@Deprecated
public IMount createResourceMount( String domain, String subPath )
{
File file = getContainingFile();
String path = "assets/" + domain + "/" + subPath;
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, "resources/main/" + path );
}
if( !wholeFile.exists() ) throw new IllegalStateException( "Cannot find ROM mount at " + file );
return new FileMount( wholeFile, 0 );
}
}
@Override
public InputStream createResourceFile( String domain, String subPath )
{
return ComputerCraft.class.getClassLoader().getResourceAsStream( "assets/" + domain + "/" + subPath );
}
private static File getContainingFile()
{
String path = ComputerCraft.class.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

@ -0,0 +1,154 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.computer;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.core.apis.ArgumentHelper;
import dan200.computercraft.core.filesystem.MemoryMount;
import dan200.computercraft.core.terminal.Terminal;
import org.apache.logging.log4j.LogManager;
import org.junit.Assert;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* Helper class to run a program on a computer.
*/
public class ComputerBootstrap
{
private static final int TPS = 20;
private static final int MAX_TIME = 10;
public static void run( String program )
{
run( program, -1 );
}
public static void run( String program, int shutdownAfter )
{
ComputerCraft.logPeripheralErrors = true;
ComputerCraft.log = LogManager.getLogger( ComputerCraft.MOD_ID );
MemoryMount mount = new MemoryMount()
.addFile( "test.lua", program )
.addFile( "startup", "assertion.assert(pcall(loadfile('test.lua', _ENV))) os.shutdown()" );
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 );
try
{
computer.turnOn();
boolean everOn = false;
for( int tick = 0; tick < TPS * MAX_TIME; tick++ )
{
long start = System.currentTimeMillis();
computer.tick();
MainThread.executePendingTasks();
if( api.message != null )
{
ComputerCraft.log.debug( "Shutting down due to error" );
computer.shutdown();
Assert.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;
// Shutdown the computer after a period of time
if( shutdownAfter > 0 && tick != 0 && tick % shutdownAfter == 0 )
{
ComputerCraft.log.info( "Shutting down: shutdown after {}", shutdownAfter );
computer.shutdown();
}
}
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();
Assert.fail( builder.toString() );
}
}
catch( InterruptedException ignored )
{
Thread.currentThread().interrupt();
}
finally
{
ComputerThread.stop();
}
}
private static class AssertApi implements ILuaAPI
{
boolean didAssert;
String message;
@Override
public String[] getNames()
{
return new String[] { "assertion" };
}
@Nonnull
@Override
public String[] getMethodNames()
{
return new String[] { "assert" };
}
@Nullable
@Override
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
{
switch( method )
{
case 0: // assert
{
didAssert = true;
Object arg = arguments.length >= 1 ? arguments[0] : null;
if( arg == null || arg == Boolean.FALSE )
{
message = ArgumentHelper.optString( arguments, 1, "Assertion failed" );
throw new LuaException( message );
}
return arguments;
}
default:
return null;
}
}
}
}

View File

@ -0,0 +1,29 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.computer;
import org.junit.Assert;
import org.junit.Test;
public class ComputerTest
{
@Test( timeout = 20_000 )
public void testTimeout()
{
try
{
ComputerBootstrap.run( "print('Hello') while true do end" );
}
catch( AssertionError e )
{
if( e.getMessage().equals( "test.lua:1: Too long without yielding" ) ) return;
throw e;
}
Assert.fail( "Expected computer to timeout" );
}
}

View File

@ -0,0 +1,143 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.filesystem;
import dan200.computercraft.api.filesystem.IWritableMount;
import javax.annotation.Nonnull;
import java.io.*;
import java.util.*;
/**
* Mounts in memory
*/
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() );
}
}
@Override
@Deprecated
public OutputStream openForWrite( @Nonnull final String path )
{
return new ByteArrayOutputStream()
{
@Override
public void close() throws IOException
{
super.close();
files.put( path, toByteArray() );
}
};
}
@Override
@Deprecated
public OutputStream 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 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 );
}
}
@Override
public long getSize( String path )
{
throw new RuntimeException( "Not implemented" );
}
@Override
@Deprecated
public InputStream openForRead( String path )
{
return new ByteArrayInputStream( files.get( path ) );
}
public MemoryMount addFile( String file, String contents )
{
files.put( file, contents.getBytes() );
return this;
}
}