├── core ├── nil │ ├── nil-spec.io │ └── control-flow-spec.io ├── message │ ├── evaluator-spec.io │ ├── construction-spec.io │ └── properties-spec.io ├── block │ ├── serialize-spec.io │ ├── scope-spec.io │ ├── call-spec.io │ └── equality-spec.io ├── exception │ ├── printing-spec.io │ └── raising-catching-spec.io ├── compiler │ └── compiler-spec.io ├── gc │ ├── correctness-spec.io │ └── will-free-spec.io ├── object │ ├── actors-spec.io │ ├── clone-spec.io │ ├── control-flow-spec.io │ └── fundamental-spec.io ├── call │ ├── arguments-spec.io │ ├── stop-status-spec.io │ ├── scopes-spec.io │ └── evaluation-spec.io └── system │ ├── utility-spec.io │ └── process-level-spec.io ├── lib ├── Mock.io └── Expectations.io ├── README.md └── iospec /core/nil/nil-spec.io: -------------------------------------------------------------------------------- 1 | describe("nil", 2 | it("has a single instance", 3 | nil clone uniqueId verify(== nil uniqueId) 4 | ) 5 | ) 6 | -------------------------------------------------------------------------------- /core/message/evaluator-spec.io: -------------------------------------------------------------------------------- 1 | describe("Message Evaluator", 2 | it("evaluates a message in a specific context", 3 | o := Object clone do(a := 42) 4 | message(a) doInContext(o) verify(== o a) 5 | ) 6 | ) 7 | -------------------------------------------------------------------------------- /core/block/serialize-spec.io: -------------------------------------------------------------------------------- 1 | describe("Block Serialization", 2 | it("serializes the block to a textual representation", 3 | blk := block(a, a +(1)) 4 | blk serialized verify(== "block(a, a +(1))\n") 5 | ) 6 | ) 7 | -------------------------------------------------------------------------------- /core/nil/control-flow-spec.io: -------------------------------------------------------------------------------- 1 | describe("nil Control Flow", 2 | it("evaluates the argument to ifNilEval", 3 | nil ifNilEval(42) verify(== 42) 4 | ) 5 | 6 | it("returns false when receiving and", 7 | nil and(42) verify(== nil) 8 | ) 9 | 10 | it("returns the argument to or", 11 | nil or(42) verify(== 42) 12 | ) 13 | ) 14 | -------------------------------------------------------------------------------- /core/block/scope-spec.io: -------------------------------------------------------------------------------- 1 | describe("Block Scope", 2 | setup( 3 | blk := block(1) setScope(Object) 4 | ) 5 | 6 | it("allows scope to be modified", 7 | a := blk clone setScope(nil) 8 | a scope verify(!= Object) 9 | ) 10 | 11 | it("self points at the scope", 12 | blk := block(self) setScope(1) 13 | blk call verify(== 1) 14 | ) 15 | ) 16 | -------------------------------------------------------------------------------- /core/message/construction-spec.io: -------------------------------------------------------------------------------- 1 | describe("Message Construction", 2 | it("can create a new message", 3 | Message clone isNil verify(not) 4 | ) 5 | 6 | it("factory creates a new message", 7 | message(a) isNil verify(not) 8 | ) 9 | 10 | it("creates a message from a string", 11 | Message clone fromString("a b") verify(compare(message(a b))) 12 | ) 13 | ) 14 | -------------------------------------------------------------------------------- /core/exception/printing-spec.io: -------------------------------------------------------------------------------- 1 | describe("Exception Printing", 2 | setup( 3 | exc := try(aNonExistentSlotName) 4 | ) 5 | 6 | it("contains an error", 7 | exc shouldReceive(error) 8 | exc error verifyType(Sequence) 9 | ) 10 | 11 | it("contains a stack trace", 12 | exc shouldReceive(showStack) 13 | // We don't mandate a certain format be used, just that a string is returned which contains a stack trace 14 | ) 15 | ) 16 | -------------------------------------------------------------------------------- /core/compiler/compiler-spec.io: -------------------------------------------------------------------------------- 1 | describe("Compiler", 2 | setup( 3 | expr := "a+(1)" 4 | ) 5 | 6 | it("returns a proper message representing the input string", 7 | compiledMessage := Compiler messageForString(expr) 8 | compiledMessage verify(isKindOf(Message)) 9 | compiledMessage verify(== message(a +(1))) 10 | ) 11 | 12 | it("components of the message are of the right kind", 13 | tokens := Compiler tokensForString(expr) map(name) 14 | tokens join("") verify(== expr) 15 | ) 16 | ) 17 | -------------------------------------------------------------------------------- /core/gc/correctness-spec.io: -------------------------------------------------------------------------------- 1 | describe("Collector correctness", 2 | setup( 3 | testCaseForFreedCount := method(Object clone; nil) 4 | ) 5 | 6 | it("keeps track of all objects in the system", 7 | Collector allObjects size verify(> 0) 8 | ) 9 | 10 | it("returns the count of how many objects were freed", 11 | Collector collect 12 | testCaseForFreedCount 13 | Collector collect verify(> 0) // In practice, this should be == 3, but this may impose implementation details on users 14 | ) 15 | ) 16 | -------------------------------------------------------------------------------- /core/gc/will-free-spec.io: -------------------------------------------------------------------------------- 1 | describe("Collector Finalization", 2 | setup( 3 | testCaseForWillFree := method( 4 | Lobby willFreeWorked := false 5 | Object clone do(willFree := method(Lobby willFreeWorked := true)) 6 | nil // So we don't accidentily keep a reference to the above object around 7 | ) 8 | ) 9 | 10 | it("executes the willFree message upon garbage collection", 11 | testCaseForWillFree 12 | Collector collect 13 | Lobby willFreeWorked verify(== true) 14 | ) 15 | ) 16 | -------------------------------------------------------------------------------- /core/object/actors-spec.io: -------------------------------------------------------------------------------- 1 | describe("Object Actors", 2 | it("schedules actors in the correct order", 3 | a := Object clone do( 4 | s := Sequence clone 5 | test := method(str, for(i, 1, 2, s appendSeq(str, i asString, "."); yield)) 6 | ) 7 | a clone @@test("a") 8 | yield 9 | a clone @@test("b") 10 | 4 repeat(yield) 11 | a s verify(== "a1.b1.a2.b2.") 12 | ) 13 | 14 | it("fires an async thread returning a future", 15 | a := Object clone 16 | a square := method(n, n * 2) 17 | future := a @square(2) 18 | future verify(== 4) 19 | ) 20 | ) 21 | -------------------------------------------------------------------------------- /core/call/arguments-spec.io: -------------------------------------------------------------------------------- 1 | describe("Call Arguments", 2 | it("has arguments", 3 | blk := block(call hasArgs) 4 | blk call verify(== false) 5 | blk call(1, 2) verify(== true) 6 | ) 7 | 8 | it("can count its arguments", 9 | block(call argCount) call(1, 2, 3) verify(== 3) 10 | ) 11 | 12 | it("gets a raw message from its arguments", 13 | result := block(call argAt(1)) call(a, b, c) 14 | result verifyType(Message) 15 | result name verify(== "b") 16 | ) 17 | 18 | it("our message is valid", 19 | block(call message) call name verify(== "call") 20 | x := method(call message) 21 | x name verify(== "x") 22 | ) 23 | ) 24 | -------------------------------------------------------------------------------- /core/object/clone-spec.io: -------------------------------------------------------------------------------- 1 | describe("Object Clone", 2 | it("creates a new empty object", 3 | Object clone slotNames verify(== list) 4 | ) 5 | 6 | it("preserves the activatable bit after a clone", 7 | orig := Object clone do(setIsActivatable(true)) 8 | other := getSlot("orig") clone 9 | getSlot("other") isActivatable verify(== true) 10 | ) 11 | 12 | it("creates a type slot automatically when given a slot name starting with an uppercase letter", 13 | Foo := Object clone 14 | Foo slotNames verify(== list("type")) 15 | ) 16 | 17 | it("proto is set to the object cloned from", 18 | o := Object clone 19 | o proto verify(== Object) 20 | o protos at(0) verify(== Object) 21 | ) 22 | ) 23 | -------------------------------------------------------------------------------- /lib/Mock.io: -------------------------------------------------------------------------------- 1 | InjectionLayer := Object clone do( 2 | on := method(target, 3 | new := self clone 4 | new setSlot("injectionLayerBit") 5 | new prependProto(target proto) 6 | target setProto(new) 7 | new 8 | ) 9 | 10 | remove := method( 11 | self become(self proto) 12 | ) 13 | ) 14 | 15 | Injectable := Object clone do( 16 | injectInto := method(target, 17 | target proto hasLocalSlot("injectionLayerBit") ifFalse( 18 | InjectionLayer on(target) 19 | ) 20 | target proto setSlot(self type, self) 21 | self 22 | ) 23 | ) 24 | 25 | Mock := Injectable clone do( 26 | mocking := method(target, 27 | new := self clone 28 | target isKindOf(Sequence) ifTrue( 29 | new type := target 30 | ) ifFalse( 31 | new prependProto(target) 32 | ) 33 | new 34 | ) 35 | ) -------------------------------------------------------------------------------- /core/object/control-flow-spec.io: -------------------------------------------------------------------------------- 1 | describe("Object Control Flow", 2 | it("evaluates its truth message on truth", 3 | if(true, "right", "wrong") verify(== "right") 4 | ) 5 | 6 | it("evaluates its false message on false or nil", 7 | if(false, "wrong", "right") verify(== "right") 8 | if(nil, "wrong", "right") verify(== "right") 9 | ) 10 | 11 | it("loops until a break is encountered", 12 | counter := 0 13 | loop( 14 | counter = counter + 1 15 | if(counter == 10, break) 16 | ) 17 | counter verify(== 10) 18 | ) 19 | 20 | it("restarts the loop when continue is hit", 21 | counter := 0 22 | loop( 23 | if(counter == 0, 24 | counter = 1 25 | continue 26 | ) 27 | break 28 | ) 29 | counter verify(== 1) 30 | ) 31 | ) 32 | -------------------------------------------------------------------------------- /core/block/call-spec.io: -------------------------------------------------------------------------------- 1 | describe("Block Call", 2 | it("invokes the block with no arguments, returning its value", 3 | blk := block(1 +(1)) 4 | blk call verify(== 2) 5 | ) 6 | 7 | it("invokes the block with the specified number of arguments, returning its value", 8 | blk := block(a, b, a +(b)) 9 | blk call(1, 2) verify(== 3) 10 | ) 11 | 12 | it("raises an exception when missing an argument", 13 | blk := block(a, a) 14 | e := try(blk call) 15 | e verify(isKindOf(Exception)) 16 | ) 17 | 18 | it("accepts additional arguments that were not specified", 19 | blk := block(call evalArgAt(0)) 20 | blk call(1) verify(== 1) 21 | ) 22 | 23 | it("activates when called by name when the activatable bit is set", 24 | blk := block(1) setIsActivatable(true) 25 | blk verify(== 1) 26 | ) 27 | ) 28 | -------------------------------------------------------------------------------- /core/exception/raising-catching-spec.io: -------------------------------------------------------------------------------- 1 | describe("Exception Raising and Catching", 2 | setup( 3 | exc := try(aNonExistentSlotName) 4 | ) 5 | 6 | it("raises a custom exception", 7 | block(Exception raise("Custom exception")) verifyException(Exception) 8 | ) 9 | 10 | it("contains a valid call object", 11 | exc originalCall verifyType(Call) 12 | ) 13 | 14 | it("contains a valid message which was the message which caused the exception to be thrown", 15 | exc originalCall message name verify(== "aNonExistentSlotName") 16 | ) 17 | 18 | it("catches a thrown exception and returns nil", 19 | exc catch(Exception) verify(== nil) 20 | ) 21 | 22 | it("catches a thrown exception and returns a value", 23 | exc catch(Exception, return 42) verify(== 42) 24 | ) 25 | 26 | pending("has a nested exception") 27 | ) 28 | -------------------------------------------------------------------------------- /core/system/utility-spec.io: -------------------------------------------------------------------------------- 1 | describe("System Utility", 2 | it("sleeps for one second", 3 | // Considered a failure if you sleep too short or too long, but understanding you'll actually sleep for more than a second. 4 | Date secondsToRun(System sleep(1)) floor verify(== 1) 5 | ) 6 | 7 | it("has a version number", 8 | System shouldReceive(version) 9 | System version verifyType(Sequence) 10 | System version size verify(> 0) 11 | ) 12 | 13 | it("has a populated symbol table", 14 | System shouldReceive(symbols) 15 | System symbols verifyType(List) 16 | System symbols size verify(> 0) 17 | ) 18 | 19 | // This seems like it should work to me, but waiting on word from Steve if his VM is broken in this respect 20 | /* 21 | it("can change its lobby", 22 | a := Object clone do(x := 42) 23 | System setLobby(a) 24 | Lobby x verify(== 42) 25 | ) 26 | */ 27 | ) 28 | -------------------------------------------------------------------------------- /core/block/equality-spec.io: -------------------------------------------------------------------------------- 1 | describe("Block Equality", 2 | setup( 3 | blk := block(1) 4 | ) 5 | 6 | it("returns true if both blocks are the same", 7 | other := getSlot("blk") 8 | other verify(== blk) 9 | ) 10 | 11 | it("returns true if both blocks have the same scope, message body and argument list are the same", 12 | a := block(1) 13 | b := block(1) 14 | a verify(== b) 15 | ) 16 | 17 | it("returns false if both blocks have different scopes", 18 | a := block(1) setScope(nil) 19 | b := block(1) setScope(Object) 20 | a verify(!= b) 21 | ) 22 | 23 | it("returns false if both blocks have different argument lists", 24 | a := block(x, x) 25 | b := block(x, y, x) 26 | a verify(!= b) 27 | ) 28 | 29 | it("returns false if both blocks have different bodies", 30 | a := block(x, y, x) 31 | b := block(x, y, y) 32 | a verify(!= b) 33 | ) 34 | ) 35 | -------------------------------------------------------------------------------- /core/call/stop-status-spec.io: -------------------------------------------------------------------------------- 1 | describe("Call Stop Status", 2 | it("sets the current stop status to break", 3 | block(call setStopStatus(Break)) call verifyType(Break) 4 | ) 5 | 6 | it("sets the current stop status to continue", 7 | block(call setStopStatus(Continue)) call verifyType(Continue) 8 | ) 9 | 10 | it("sets the current stop status to return", 11 | block(call setStopStatus(Return)) call verifyType(Return) 12 | ) 13 | 14 | it("sets the current stop status to normal", 15 | block( 16 | // First so we know it's a dirty value 17 | call setStopStatus(Break) 18 | call setStopStatus(Normal) 19 | ) call verifyType(Normal) 20 | ) 21 | 22 | it("resets the current stop status to normal", 23 | block( 24 | // First so we know it's a dirty value 25 | call setStopStatus(Break) 26 | call resetStopStatus 27 | ) call verifyType(Normal) 28 | ) 29 | ) 30 | -------------------------------------------------------------------------------- /core/system/process-level-spec.io: -------------------------------------------------------------------------------- 1 | describe("System Process", 2 | it("able to exit the interpreter", 3 | System shouldReceive(exit) 4 | ) 5 | 6 | it("knows its platform", 7 | System shouldReceive(platform) 8 | System platform verifyType(Sequence) 9 | System platform size verify(> 0) 10 | ) 11 | 12 | it("knows its platform version", 13 | System shouldReceive(platformVersion) 14 | System platformVersion verifyType(Sequence) 15 | System platformVersion size verify(> 0) 16 | ) 17 | 18 | it("knows its launch path", 19 | System shouldReceive(launchPath) 20 | System launchPath verifyType(Sequence) 21 | System launchPath size verify(> 0) 22 | ) 23 | 24 | it("knows its install prefix", 25 | System shouldReceive(installPrefix) 26 | System launchPath verifyType(Sequence) 27 | System launchPath size verify(> 0) 28 | ) 29 | 30 | it("knows the path to its lib dir", 31 | System shouldReceive(ioPath) 32 | System ioPath verifyType(Sequence) 33 | System ioPath size verify(> 0) 34 | ) 35 | ) 36 | -------------------------------------------------------------------------------- /core/call/scopes-spec.io: -------------------------------------------------------------------------------- 1 | describe("Call Scopes", 2 | it("has a sender, and it is the current context", 3 | block(call sender) call verify(== thisContext) 4 | method(call sender) call verify(== thisContext) 5 | ) 6 | 7 | it("knows that the activated block was ourselves", 8 | blk := block(call activated) 9 | mth := method(call activated) 10 | blk call verify(== blk) 11 | mth call verify(== mth) 12 | ) 13 | 14 | it("the target represents the receiver of the method invocation", 15 | o := Object clone do( 16 | blk := block(call target) setIsActivatable(true) 17 | mth := method(call target) 18 | ) 19 | 20 | o blk verify(== o) 21 | o mth verify(== o) 22 | ) 23 | 24 | it("a lexical blocks self pointer points at the context in which it is scoped", 25 | a := method(self b := block(self)) 26 | a 27 | b call verifyType(a) 28 | ) 29 | 30 | it("knows where its slot is defined", 31 | a := Object clone do(foo := method(call slotContext)) 32 | b := a clone 33 | b foo verifyType(a) 34 | ) 35 | ) 36 | -------------------------------------------------------------------------------- /lib/Expectations.io: -------------------------------------------------------------------------------- 1 | Expectations := Object clone 2 | 3 | Object verify := method( 4 | checkMessage := call argAt(0) 5 | checkCode := call message code 6 | 7 | if(checkMessage name == "==", 8 | other := call sender doMessage(checkMessage argAt(0), call sender) 9 | 10 | if(self == other, 11 | return self 12 | , 13 | AssertionFailed raise(self asSimpleString .. " != " .. other asSimpleString .. ". " .. checkCode) 14 | ) 15 | ) 16 | 17 | if(self doMessage(checkMessage, call sender), 18 | return self 19 | , 20 | AssertionFailed raise(checkCode) 21 | ) 22 | ) 23 | 24 | Object verifyType := method(anObject, 25 | if(anObject isKindOf(anObject), 26 | self 27 | , 28 | AssertionFailed raise(self asSimpleString .. " != " .. anObject asSimpleString) 29 | ) 30 | ) 31 | 32 | Block verifyException := method(exception, 33 | e := try(self call) 34 | e verifyType(exception) 35 | ) 36 | 37 | Object shouldReceive := method( 38 | name := call argAt(0) name 39 | if(self getSlot(name), 40 | self 41 | , 42 | AssertionFailed raise(self asSimpleString .. " does not respond to '" .. name .. "'") 43 | ) 44 | ) 45 | -------------------------------------------------------------------------------- /core/call/evaluation-spec.io: -------------------------------------------------------------------------------- 1 | describe("Call Evaluation", 2 | it("evaluates all its arguments", 3 | block(call evalArgs) call(1, 2) verify(== list(1, 2)) 4 | ) 5 | 6 | it("evalutes an arbitrary argument only", 7 | block(call evalArgAt(1)) call("unevaluated", "evaluated") verify(== "evaluated") 8 | ) 9 | 10 | it("delegates the calling message to a different target", 11 | o := Object clone do(a := method(42)) 12 | a := method(call delegateTo(o)) 13 | a verify(== 42) 14 | ) 15 | 16 | it("delegates the calling message to a different target with an alternative sender", 17 | o := Object clone do(a := method(call sender)) 18 | a := method(call delegateTo(o, o)) 19 | a verify(== o) 20 | ) 21 | 22 | it("preserves the stop status when delegating to another target", 23 | o := Object clone do(a := method(call stopStatus)) 24 | a := method(call setStopStatus(Break); call delegateTo(o)) 25 | a verify(== Break) 26 | ) 27 | 28 | it("delegates the calling message to a different target via a different method", 29 | o := Object clone do(b := method(42)) 30 | a := method(call delegateToMethod(o, "b")) 31 | a verify(== 42) 32 | ) 33 | ) 34 | -------------------------------------------------------------------------------- /core/object/fundamental-spec.io: -------------------------------------------------------------------------------- 1 | describe("Object Fundamentals", 2 | setup( 3 | ctx := Object clone do(setSlot("test", 42)) 4 | ) 5 | 6 | it("sets a slot on the object and is able to retrieve its value with a send", 7 | ctx test verify(== 42) 8 | ) 9 | 10 | it("can lookup a known slot on an object, but not find an unknown slot", 11 | ctx getSlot("test") verify(== 42) 12 | ctx getSlot("fake") verify(== nil) 13 | ) 14 | 15 | it("has the right object as its proto", 16 | ctx proto verify(== Object) 17 | ctx protos at(0) verify(== Object) 18 | ) 19 | 20 | it("finds the right slot on a proto", 21 | a := ctx clone 22 | a test verify(== 42) 23 | ) 24 | 25 | it("finds a slot on a different proto, one that isn't the first proto", 26 | a := ctx clone 27 | a appendProto(Object clone do(x := 23)) 28 | a x verify(== 23) 29 | ) 30 | 31 | it("removes all protos and raises an exception when receiving a message", 32 | block(Object clone removeAllProtos clone) verifyException(Exception) 33 | ) 34 | 35 | it("tells whether or not a slot exists on the object, or any of its descendents", 36 | ctx hasSlot("test") verify(== true) 37 | ctx hasSlot("fake") verify(== false) 38 | other := ctx clone 39 | other hasSlot("test") verify(== true) 40 | ) 41 | 42 | it("is able to remove a slot from an object", 43 | ctx removeSlot("test") 44 | ctx hasLocalSlot("test") verify(== false) 45 | ) 46 | ) 47 | -------------------------------------------------------------------------------- /core/message/properties-spec.io: -------------------------------------------------------------------------------- 1 | describe("Message Properties", 2 | it("has a name", 3 | message(a) name verify(== "a") 4 | ) 5 | 6 | it("has an argument list", 7 | message(a(1, 2)) arguments verify(compare(list(1, 2))) 8 | ) 9 | 10 | it("has exactly two arguments", 11 | message(a(1, 2)) argCount verify(== 2) 12 | ) 13 | 14 | it("gets the second argument", 15 | message(a(1, 2)) argAt(1) isNil verify(not) 16 | ) 17 | 18 | it("appends an argument", 19 | message(a(1)) appendArg(message(2)) arguments verify(compare(list(1, 2))) 20 | ) 21 | 22 | it("replaces the argument list with another", 23 | m1 := message(a(1)) 24 | m2 := message(a(2)) 25 | m1 setArguments(list(message(2))) verify(compare(m2)) 26 | ) 27 | 28 | it("retrieves the next message", 29 | message(a b) next isNil verify(not) 30 | ) 31 | 32 | it("next message has the right name", 33 | message(a b) next name verify(== "b") 34 | ) 35 | 36 | it("sets the next message to another message", 37 | m := message(a b) 38 | o := message(c d) 39 | m setNext(o) verify(compare(message(a c d))) 40 | ) 41 | 42 | it("retrieves the previous message", 43 | m := message(a b) 44 | m next previous verify(== m) 45 | ) 46 | 47 | it("can be represented as a string", 48 | message(a b) asString verify(== "a b") 49 | ) 50 | 51 | it("cachedResult is nil", 52 | message(a) cachedResult verify(== nil) 53 | ) 54 | 55 | it("has no cached result", 56 | message(a) hasCachedResult verify(== false) 57 | ) 58 | 59 | it("sets a cached result", 60 | m := message(a) setCachedResult(42) 61 | doMessage(m) verify(== 42) 62 | ) 63 | 64 | it("detects if we are at the end of the line", 65 | message(a ; b) next isEndOfLine verify(== true) 66 | ) 67 | ) 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IoSpec - The Io Programming Language Specification 2 | 3 | This project aims to build a complete executable specification of the [Io Programming Language](http://iolanguage.com/). We do this so that the canonical IoVM of the day doesn't slow progress by stagnating its own development, when other implementations may put forward ideas to further the language. There are presnetly several implementations of Io under active development. 4 | 5 | @stevedekorte started the Io programming language in 2002, and has implemented the most popular implementation; the canonical version. The goal of this project is to get rid of any one canonical version, and provide a base for all implementations to conform to, to be considered implementations of the Io programming language. This would apply to Steve's version as well. 6 | 7 | Steve and I have talked about this project in brief, and we're both ok with taking it in this direction. More details will be forthcoming as we pick our testing framework, and start writing the tests. In general, a few things we want to do. 8 | 9 | ## Current Version 10 | 11 | 0.1.0 is the latest version of the specification. 12 | 13 | ## Goals 14 | 15 | 1. Test the core semantics of the language. Things like message sending, argument handling, exceptions, etc. 16 | 2. Test the required APIs. This won't include any addons, for instance as they're not part of the language. 17 | 3. Most important, this should function across implementations. 18 | 19 | ## Contributing 20 | 21 | A few things to note. We use a primitive testing tool which is a fork of [Jonathan Wright's iospec2](https://github.com/quag/iospec2). If you want to help us build on that, awesome! We could use some help building up our table of expectations. 22 | 23 | If you instead (or in addition to) want to help us build up the language spec, then that'd be great. The filename convention is `thingYoureTesting-spec.io`. Anything else and it won't be picked up by the automatic runner (feel free to improve that if you want too). Look at other specs for tips on how to write specs. If you want some more expressive power, or things like mocks, we'll need to build those into the testing tool. Send pull requests when you run into those and fix them. 24 | 25 | ## License 26 | 27 | This specification is covered by the MIT license, which allows permissive use of the code. By contributing anything to this repository, you are implicitly agreeing to be bound by those conditions, and grant others permission to use your submissions under those terms, irrevokably. 28 | 29 | ``` 30 | Copyright (c) 2011, Jeremy Tregunna and others, All Rights Reserved. 31 | 32 | Permission is hereby granted, free of charge, to any person obtaining 33 | a copy of this software and associated documentation files (the 34 | "Software"), to deal in the Software without restriction, including 35 | without limitation the rights to use, copy, modify, merge, publish, 36 | distribute, sublicense, and/or sell copies of the Software, and to 37 | permit persons to whom the Software is furnished to do so, subject to 38 | the following conditions: 39 | 40 | The above copyright notice and this permission notice shall be 41 | included in all copies or substantial portions of the Software. 42 | 43 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 44 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 45 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 46 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 47 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 48 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 49 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 50 | ``` 51 | 52 | -------------------------------------------------------------------------------- /iospec: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env io 2 | 3 | Importer addSearchPath("lib") 4 | 5 | IoSpec := Object clone do( 6 | version := "0.1.0" 7 | 8 | Expectations 9 | ) 10 | 11 | // What else should we do here? If it's not defined, we define it to be our current version. Probably better than nothing. 12 | if(System getSlot("iospecVersion") isNil, 13 | "WARNING: Your IoVM does not understand iospecVersion, defaulting to current IoSpec version." println 14 | System iospecVersion := IoSpec version 15 | ) 16 | 17 | AssertionFailed := Exception clone 18 | 19 | BodyContext := Object clone do( 20 | newSlot("setupMessage", message(nil)) 21 | newSlot("teardownMessage", message(nil)) 22 | newSlot("parent", nil) 23 | newSlot("bodyContextName", nil) 24 | newSlot("quiet", true) 25 | 26 | setup := method( 27 | setupMessage = call argAt(0) 28 | ) 29 | 30 | teardown := method( 31 | teardownMessage = call argAt(0) 32 | ) 33 | 34 | describe := method( 35 | describedState := call evalArgAt(0) 36 | bodyMessage := call argAt(1) 37 | 38 | if(describedState type != "Sequence", 39 | describedState = describedState type 40 | ) 41 | 42 | bodyContext := BodyContext clone setQuiet(quiet) setParent(self) 43 | if(bodyContextName != nil, 44 | bodyContext setBodyContextName(bodyContextName .. " " .. describedState) 45 | , 46 | bodyContext setBodyContextName(describedState) 47 | ) 48 | 49 | bodyContext setSlot("it", 50 | method(shouldName, 51 | Lobby exampleCount = exampleCount + 1 52 | 53 | testContext := Object clone 54 | e := try( 55 | describeContext := self 56 | describeContexts := list 57 | while(describeContext != nil, 58 | describeContexts prepend(describeContext) 59 | describeContext = describeContext parent 60 | ) 61 | 62 | describeContexts foreach(setupMessage doInContext(testContext)) 63 | call argAt(1) doInContext(testContext) 64 | describeContexts foreach(teardownMessage doInContext(testContext)) 65 | ) 66 | if(e, 67 | failureErrors append(e) 68 | quiet ifTrue(write("F"); File standardOutput flush) ifFalse(" - #{shouldName} [Error #{failureErrors size}]" interpolate println) 69 | , 70 | quiet ifTrue(write("."); File standardOutput flush) ifFalse(" - #{shouldName}" interpolate println) 71 | ) 72 | ) 73 | ) 74 | 75 | bodyContext setSlot("itDependsOnVersion", 76 | method( 77 | versionPredicate := call argAt(0) 78 | shouldName := call evalArgAt(1) 79 | if(System iospecVersion doMessage(versionPredicate), 80 | self performWithArgList("it", list(shouldName, call evalArgAt(2))) 81 | ) 82 | ) 83 | ) 84 | 85 | bodyContext setSlot("pending", 86 | method(str, 87 | pendingSpecs append(str) 88 | quiet ifTrue("*" print) ifFalse(" - #{str} [Pending #{pendingSpecs size}]" interpolate println) 89 | ) 90 | ) 91 | 92 | hasTests := false 93 | m := bodyMessage 94 | while(m != nil, 95 | if(m name == "it", 96 | hasTests = true 97 | break 98 | ) 99 | m = m next 100 | ) 101 | 102 | if(hasTests and quiet not, 103 | writeln(bodyContext bodyContextName) 104 | ) 105 | bodyMessage ?doInContext(bodyContext) 106 | if(hasTests and quiet not, 107 | writeln 108 | ) 109 | ) 110 | ) 111 | 112 | 113 | exampleCount := 0 114 | failureErrors := list 115 | pendingSpecs := list 116 | quiet := true 117 | 118 | args := System args 119 | if(args sort at(0) == "-v", 120 | args removeFirst 121 | quiet = false 122 | ) 123 | 124 | if(System args size > 1, 125 | specs := System args exSlice(1) 126 | , 127 | specs := Directory recursiveFilesOfTypes(list("-spec.io")) map(path) 128 | ) 129 | 130 | writeln 131 | time := Date secondsToRun( 132 | specs foreach(spec, 133 | BodyContext clone setQuiet(quiet) doFile(spec) 134 | ) 135 | ) 136 | writeln("\n") 137 | 138 | pendingSpecs foreach(i, pending, 139 | "Pending #{i + 1}: #{pending}" interpolate println 140 | ) 141 | writeln 142 | 143 | failureErrors foreach(i, error, 144 | write("Error ", i + 1, ":") 145 | error showStack 146 | ) 147 | failureCount := failureErrors size 148 | 149 | writeln("\nFinished in ", time, " seconds") 150 | writeln 151 | writeln(if(specs size == 1, "", specs size .. " specs, "), exampleCount, if(exampleCount == 1, " example, ", " examples, "), failureCount, if(failureCount == 1, " failure,", " failures,"), if(pendingSpecs size == 1, " 1 pending", pendingSpecs size .. " pending")) 152 | --------------------------------------------------------------------------------