Groundwork for rewriting + testing Story
This commit is contained in:
@@ -20,6 +20,10 @@ class HInterface {
|
||||
}
|
||||
}
|
||||
|
||||
public function getVariable(v: String) {
|
||||
return interp.variables[v];
|
||||
}
|
||||
|
||||
/**
|
||||
Run a pre-processed block of Haxe embedded in a Hank story.
|
||||
**/
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package tests;
|
||||
package hank;
|
||||
|
||||
import utest.Assert;
|
||||
|
||||
@@ -59,14 +59,12 @@ class Output {
|
||||
var peekLine = buffer.peekLine().unwrap();
|
||||
if (peekLine.length < endSegment) {
|
||||
var text = buffer.takeLine().unwrap();
|
||||
trace(text);
|
||||
if (text.length > 0) {
|
||||
parts.push(Text(text));
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
var text = buffer.take(endSegment);
|
||||
trace(text);
|
||||
parts.push(Text(text));
|
||||
}
|
||||
} else {
|
||||
@@ -143,4 +141,14 @@ class Output {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function format(): String {
|
||||
var fullOutput = '';
|
||||
|
||||
for (part in parts) {
|
||||
|
||||
}
|
||||
|
||||
return fullOutput;
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,59 @@
|
||||
package hank;
|
||||
|
||||
import hank.HInterface;
|
||||
using HankAST.ASTExtension;
|
||||
import hank.HankAST.ExprType;
|
||||
|
||||
/**
|
||||
Possible states of the story being executed.
|
||||
**/
|
||||
enum StoryFrame {
|
||||
HasText(text: String);
|
||||
HasChoices(choices: Array<String>);
|
||||
Finished;
|
||||
}
|
||||
|
||||
/**
|
||||
Runtime interpreter for Hank stories.
|
||||
**/
|
||||
@:allow(hank.StoryTestCase)
|
||||
class Story {
|
||||
var hInterface: HInterface;
|
||||
var random: Random;
|
||||
|
||||
var ast: HankAST;
|
||||
var exprIndex: Int;
|
||||
|
||||
var parser: Parser;
|
||||
|
||||
public function new(script: String, ?randomSeed: Int) {
|
||||
random = new Random(randomSeed);
|
||||
|
||||
parser = new Parser();
|
||||
ast = parser.parseFile(script);
|
||||
// viewCounts = new ViewCounts(ast);
|
||||
|
||||
public function new() {
|
||||
var variables = [
|
||||
'story' => this
|
||||
'story' => this/*,
|
||||
'viewCounts' => viewCounts
|
||||
*/
|
||||
];
|
||||
hInterface = new HInterface(variables);
|
||||
|
||||
exprIndex = ast.findFile(script);
|
||||
}
|
||||
|
||||
public function nextFrame(): StoryFrame {
|
||||
switch (ast[exprIndex].expr) {
|
||||
case EOutput(output):
|
||||
return HasText(output.format());
|
||||
default:
|
||||
return Finished;
|
||||
}
|
||||
return Finished;
|
||||
}
|
||||
|
||||
public function choose(choiceIndex: Int): String {
|
||||
return '';
|
||||
}
|
||||
|
||||
/** Parse and run embedded Hank script on the fly. **/
|
||||
|
||||
66
hank/StoryTestCase.hx
Normal file
66
hank/StoryTestCase.hx
Normal file
@@ -0,0 +1,66 @@
|
||||
package hank;
|
||||
|
||||
import hank.Story.StoryFrame;
|
||||
|
||||
class StoryTestCase extends utest.Test {
|
||||
private function validateAgainstTranscript(storyFile: String, transcriptFile: String, fullTranscript: Bool = true) {
|
||||
|
||||
var transcriptLines = sys.io.File.getContent(transcriptFile).split('\n');
|
||||
// If the transcript starts with a random seed, make sure the story uses that seed
|
||||
var randomSeed = null;
|
||||
if (StringTools.startsWith(transcriptLines[0], '@')) {
|
||||
randomSeed = Std.parseInt(transcriptLines[0].substr(1));
|
||||
transcriptLines.remove(transcriptLines[0]);
|
||||
}
|
||||
|
||||
var story: Story = new Story(storyFile, randomSeed);
|
||||
|
||||
var i = 0;
|
||||
while (i < transcriptLines.length) {
|
||||
var line = transcriptLines[i];
|
||||
var frame = story.nextFrame();
|
||||
|
||||
// Allow white-box story testing through variable checks prefixed with #
|
||||
if (StringTools.startsWith(line, "#")) {
|
||||
var parts = StringTools.trim(line.substr(1)).split(':');
|
||||
HankAssert.equals(StringTools.trim(parts[1]), Std.string(story.hInterface.getVariable(parts[0])));
|
||||
}
|
||||
if (StringTools.startsWith(line, "*")) {
|
||||
// Collect the expected set of choices from the transcript.
|
||||
var choices = new Array<String>();
|
||||
do {
|
||||
choices.push(line.substr(2));
|
||||
|
||||
line = transcriptLines[++i];
|
||||
} while (line != null && StringTools.startsWith(line, "*"));
|
||||
|
||||
// Assert that the storyframe is a corresponding HasChoices enum
|
||||
HankAssert.equals(HasChoices(choices), frame);
|
||||
|
||||
continue;
|
||||
} else if (StringTools.startsWith(line, ">")) {
|
||||
// Make the choice given, and check for expected output.
|
||||
line = StringTools.ltrim(line.substr(1));
|
||||
var firstColonIdx = line.indexOf(':');
|
||||
var index = Std.parseInt(line.substr(0, firstColonIdx))-1;
|
||||
var expectedOutput = StringTools.trim(line.substr(firstColonIdx+1));
|
||||
// trace('expecting: ${expectedOutput}');
|
||||
var output = story.choose(index);
|
||||
// trace('got: ${output}');
|
||||
HankAssert.equals(expectedOutput, output);
|
||||
}
|
||||
else if (line.length > 0) {
|
||||
// Assert that the story's next frame is HasText(line)
|
||||
// trace('${line} from ${frame}');
|
||||
HankAssert.equals(HasText(line), frame);
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
if (fullTranscript) {
|
||||
// After all transcript lines are validated, there should be nothing left in the story flow!
|
||||
HankAssert.equals(StoryFrame.Finished, story.nextFrame());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ using hank.Extensions.OptionExtender;
|
||||
import haxe.ds.Option;
|
||||
import hank.HankBuffer;
|
||||
import hank.HankBuffer.Position;
|
||||
import hank.HankAssert;
|
||||
|
||||
class HankBufferTest extends utest.Test {
|
||||
var file: HankBuffer;
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package tests;
|
||||
|
||||
import hank.Parser;
|
||||
import hank.Parser.HankAST;
|
||||
import hank.HankAST;
|
||||
import hank.Output;
|
||||
import hank.Alt;
|
||||
import hank.HankAssert;
|
||||
|
||||
class ParserTest extends utest.Test {
|
||||
var ast: HankAST;
|
||||
@@ -80,7 +81,7 @@ class ParserTest extends utest.Test {
|
||||
ToggleOutput(new Output([Text(".")]), false),
|
||||
ToggleOutput(new Output([Text(" that changes after a choice is made!")]), true)
|
||||
])));
|
||||
trace(nextExpr());
|
||||
// TODO test the last one
|
||||
}
|
||||
|
||||
|
||||
|
||||
26
tests/StoryTest.hx
Normal file
26
tests/StoryTest.hx
Normal file
@@ -0,0 +1,26 @@
|
||||
package tests;
|
||||
|
||||
class StoryTest extends hank.StoryTestCase {
|
||||
function testAllExamples() {
|
||||
var exampleFolders = sys.FileSystem.readDirectory('examples');
|
||||
|
||||
// Iterate through every example in the examples folder
|
||||
for (folder in exampleFolders) {
|
||||
trace('Running tests for example "${folder}"');
|
||||
var files = sys.FileSystem.readDirectory('examples/${folder}');
|
||||
|
||||
for (file in files) {
|
||||
if (StringTools.endsWith(file, '.hlog')) {
|
||||
trace(' Running ${file}');
|
||||
|
||||
var disabled = file.indexOf("disabled") != -1;
|
||||
var debug = file.indexOf("debug") != -1;
|
||||
var partial = file.indexOf("partial") != -1;
|
||||
if (!disabled) {
|
||||
validateAgainstTranscript('examples/${folder}/main.hank', 'examples/${folder}/${file}', !partial);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,6 @@ import utest.Test;
|
||||
|
||||
class TestMain extends Test {
|
||||
public static function main() {
|
||||
utest.UTest.run([new HInterfaceTest(), new HankBufferTest(), new ParserTest()]);
|
||||
utest.UTest.run([new HInterfaceTest(), new HankBufferTest(), new ParserTest(), new StoryTest()]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user