class ExpectObj { private inverted=false; private currentSpec; private currentTest; private left; public getNot() { this.inverted = !this.inverted; return this; } public ExpectObj(currentTest,currentSpec,left) { this.currentTest = currentTest; this.currentSpec = currentSpec; this.left = left; } public ToBe(right) { if(this.inverted) { this.SetRes(this.left != right, $"{this.left} is equal to {this.right}"); } else { this.SetRes(this.left == right, $"{this.left} is not equal to {right}"); } } public LessThan(right) { if(this.inverted) { this.SetRes(!(this.left < right), $"{this.left} is less than {this.right}"); } else { this.SetRes(this.left < right, $"{this.left} is not less than {right}"); } } public GreaterThan(right) { if(this.inverted) { this.SetRes(!(this.left > right), $"{this.left} is greater than {this.right}"); } else { this.SetRes(this.left > right, $"{this.left} is not greater than {right}"); } } private SetRes(succeeded, failmsg) { if(!succeeded) { this.currentTest.success = false; this.currentSpec.pass=false; this.currentSpec.reason = failmsg; } } } func Tesses.CrossLang.Shell.Test(dd) { var offline=false; var buildPath = "."; var nobuild=false; var allowFullCompTime=false; var output=""; var debug = false; each(var flag : dd.Flags) { if(flag == "debug") { debug = true; } else if(flag == "offline") { offline = true; } else if(flag == "allow-insecure-comptime") { allowFullCompTime=true; } else if(flag == "help") { Console.WriteLine("USAGE: crosslang test [run-flags-and-options] program-arguments..."); Console.WriteLine("USAGE: crosslang test [run-flags-and-options] -- program-arguments-and-options..."); Console.WriteLine("FLAGS:"); Console.WriteLine("offline: build with no internet (don't fetch files)"); Console.WriteLine("allow-insecure-comptime: Allow full comptime"); Console.WriteLine("nobuild: don't build, just run"); Console.WriteLine("debug: emit line info into executable"); Console.WriteLine("help: this help"); Console.WriteLine(); Console.WriteLine("OPTIONS:"); Console.WriteLine("conf=CONFIGSTRING: specify a conf string for compile_tool(s), is the property Config"); return 0; } else if(flag == "nobuild") { nobuild=true; } } var conf = ""; each(var option : dd.Options) { if(option.Key == "conf") { conf = option.Value; } } if(nobuild) { if(FS.Local.FileExists("cross.json")) { var proj = Json.Decode(FS.ReadAllText(FS.Local, "cross.json")); var nameVer = $"{proj.name}-{proj.version}.crvm"; var buildDir = TypeOf(proj.bin_directory) == "String" ? proj.bin_directory : "bin"; output =./buildDir/nameVer; if(!FS.Local.FileExists(output)) { Console.WriteLine($"ERROR: file {output} does not exist."); } } else { Console.WriteLine("ERROR: could not find project."); return 1; } } else { var pm = new Tesses.CrossLang.PackageManager(); pm.Offline = offline; var bt = new Tesses.CrossLang.BuildTool(pm); bt.Config = conf; bt.Debug = debug; bt.AllowFullCompTime = allowFullCompTime; output = bt.BuildProject(buildPath).Output; } return Tesses.CrossLang.Shell.RunTestSuite(output); } func Tesses.CrossLang.Shell.RunTestSuite(execFile) { var configData = Json.Decode(FS.ReadAllText(FS.Local,"cross.json")) ?? {}; var testDir = configData.test_directory ?? "tests"; var sources = []; func walk_for_source(sourceDir) { if(FS.Local.DirectoryExists(sourceDir)) each(var file : FS.Local.EnumeratePaths(sourceDir)) { if(FS.Local.RegularFileExists(file) && file.GetExtension()==".tcross") { var src = { FileName = file.ToString(), Source = FS.ReadAllText(FS.Local, file) }; sources.Add(src); } else if(FS.Local.DirectoryExists(file)) { walk_for_source(file); } } } walk_for_source(testDir); const strm = new MemoryStream(true); var result = VM.Compile({ Name = "/test/", Version = "1.0.0.0-prod", Sources = sources, Dependencies = [], //they are loaded for us Output = strm, CompTime = compTimeEnv, Debug = this.Debug }); if(result.Success) { const RED = "\e[0;91m"; const GREEN = "\e[0;92m"; const YELLOW = "\e[;93m"; const NC = "\e[0m"; strm.Seek(0,0); var vmFile = VM.LoadExecutable(strm); strm.GetBytes().Resize(0); var env = VM.CreateEnvironment({}); env.RegisterEverything(); env.LockRegister(); env.LoadFileWithDependencies(FS.Local,execFile); var failed = 0; var total = 0; var currentTest = null; var currentSpec = null; func Test(name, cb) { total++; currentTest = { success = true, specs = [] }; testLists.Add(currentTest); try { cb(); if(!currentTest.success) { failed++; Console.WriteLine($"[{RED}FAILED{NC}] {name}"); each(var s : currentTest.specs) { if(s.todo) { Console.WriteLine($"\t[{YELLOW}TODO{NC}] {s.name}"); } else if(s.pass) { Console.WriteLine($"\t[{GREEN}PASS{NC}] {s.name}"); } else { Console.WriteLine($"\t[{RED}FAILED{NC}] {s.name}"); Console.WriteLine($"\t\tReason: {s.reason}"); } } } else { Console.WriteLine($"[{GREEN}PASS{NC}] {name}"); each(var s : currentTest.specs) { if(s.todo) { Console.WriteLine($"\t[{YELLOW}TODO{NC}] {s.name}"); } else if(s.pass) { Console.WriteLine($"\t[{GREEN}PASS{NC}] {s.name}"); } } } } catch(ex) { Console.WriteLine($"[{RED}EXCEPTION{NC}] {name}"); Console.WriteLine($"\tReason: {ex}"); each(var s : currentTest.specs) { if(s.todo) { Console.WriteLine($"\t[{YELLOW}TODO{NC}] {s.name}"); } else if(s.pass) { Console.WriteLine($"\t[{GREEN}PASS{NC}] {s.name}"); } else { Console.WriteLine($"\t[{RED}FAILED{NC}] {s.name}"); Console.WriteLine($"\t\tReason: {s.reason}"); } } failed++; } currentTest = null; } func SpecToDo(name) { if(TypeIsDictionary(currentSpec)) { throw "Must not be in Spec"; } if(TypeIsDictionary(currentTest)) { currentTest.specs.Add({ todo = true, name }); } else { throw "Must be within Test(\"My test name\",()=>{});"; } } func Spec(name,cb) { if(TypeIsDictionary(currentSpec)) { throw "Must not be in Spec"; } if(TypeIsDictionary(currentTest)) { currentSpec = { name }; currentTest.specs.Add(currentSpec); cb(); currentSpec.pass ??= true; currentSpec = null; } else { throw "Must be within Test(\"My test name\",()=>{});"; } } func Expect(val) { if(TypeIsDictionary(currentTest) && TypeIsDictionary(currentSpec)) { return new ExpectObj(currentTest,currentSpec,val); } else { throw "Must be within Spec(\"My spec name\",()=>{});"; } } func Assert(val, failmsg) { if(TypeIsDictionary(currentTest) && TypeIsDictionary(currentSpec)) { if(val) else { currentSpec.pass=false; currentSpec.reason = failmsg; currentTest.success=false; } } else { throw "Must be within Spec(\"My spec name\",()=>{});"; } } env.SetVariable("Test", Test); env.SetVariable("Spec", Spec); env.SetVariable("SpecToDo", SpecToDo); env.SetVariable("Expect", Expect); env.SetVariable("Assert",Assert); const beginTime = DateTime.Now; try { env.LoadFile(vmFile); } catch(ex) { Console.WriteLine($"[{RED}EXCEPTION{NC}] {ex}"); return 1; } Console.WriteLine(); if(failed == 0) { Console.WriteLine($"Tests {GREEN}passed{NC} in {(DateTime.Now-beginTime).ToString(true)}"); } else { Console.WriteLine($"Tests {RED}failed{NC} in {(DateTime.Now-beginTime).ToString(true)}"); } Console.WriteLine($"Passed: {total-failed}"); Console.WriteLine($"Failed: {failed}"); Console.WriteLine($"Total: {total}"); return failed > 0 ? 1 : 0; } return 0; }