Skip to content

Commit d6a7838

Browse files
committed
com.livecode.unittest: Add new unit testing syntax.
Add a new unit test library that outputs results on stdout using the Test Anything Protocol <http://testanything.org/>.
1 parent 180490a commit d6a7838

File tree

3 files changed

+254
-2
lines changed

3 files changed

+254
-2
lines changed

libscript/libstdscript-modules.list

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,5 @@ stream.mlc
1717
string.mlc
1818
system.mlc
1919
type.mlc
20-
type-convert.mlc
20+
type-convert.mlc
21+
unittest.mlc

libscript/src/unittest.mlc

Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
/*
2+
Copyright (C) 2015 Runtime Revolution Ltd.
3+
4+
This file is part of LiveCode.
5+
6+
LiveCode is free software; you can redistribute it and/or modify it under
7+
the terms of the GNU General Public License v3 as published by the Free
8+
Software Foundation.
9+
10+
LiveCode is distributed in the hope that it will be useful, but WITHOUT ANY
11+
WARRANTY; without even the implied warranty of MERCHANTABILITY or
12+
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13+
for more details.
14+
15+
You should have received a copy of the GNU General Public License
16+
along with LiveCode. If not see <http://www.gnu.org/licenses/>. */
17+
18+
/*
19+
This library provides syntax for unit testing LiveCode Builder
20+
programs. It is used by the LiveCode Builder standard library's
21+
testsuite.
22+
23+
To use this library, write your tests in a Builder source code file.
24+
Each group of tests should be a public handler with a name beginning
25+
with "Test". If possible, use one test per handler. Otherwise, add a
26+
"plan N tests" statement at the start of the handler.
27+
28+
Example:
29+
30+
public handler TestSelf()
31+
plan 10 tests
32+
33+
test diagnostic "Normal tests"
34+
test 2 > 1
35+
test "Basic test" when true
36+
37+
test diagnostic "Skipped tests"
38+
skip test
39+
skip test "Skipped 2"
40+
skip test because "Not implemented"
41+
skip test "Skipped 4" because "Not supported on this platform"
42+
43+
test diagnostic "Tests which are expected to fail"
44+
test fails when false
45+
test "Failed 2" fails when false
46+
test fails when false because "broken"
47+
test "Failed 4" fails when false because "really broken"
48+
end handler
49+
50+
The test results are output on standard output in TAP (Test Anything
51+
Protocol) format.
52+
*/
53+
54+
module com.livecode.unittest
55+
56+
use com.livecode.system
57+
use com.livecode.stream
58+
59+
----------------------------------------------------------------
60+
-- Test syntax
61+
----------------------------------------------------------------
62+
63+
syntax UnitPlan is statement
64+
"plan" <Count: Expression> "tests"
65+
begin
66+
MCUnitPlan(Count)
67+
end syntax
68+
69+
syntax UnitDiagnostic is statement
70+
"test" "diagnostic" <Message: Expression>
71+
begin
72+
MCUnitDiagnostic(Message)
73+
end syntax
74+
75+
----------------------------------------------------------------
76+
77+
syntax UnitTest is statement
78+
"test" <Condition: Expression>
79+
begin
80+
MCUnitTest(Condition)
81+
end syntax
82+
83+
public handler MCUnitTest(in pCondition as boolean)
84+
MCUnitOutputTest(pCondition, "", "", "")
85+
end handler
86+
87+
syntax UnitTestDescription is statement
88+
"test" <Description: Expression> "when" <Condition: Expression>
89+
begin
90+
MCUnitTestDescription(Description, Condition)
91+
end syntax
92+
93+
public handler MCUnitTestDescription(in pDescription as string, in pCondition as boolean)
94+
MCUnitOutputTest(pCondition, pDescription, "", "")
95+
end handler
96+
97+
----------------------------------------------------------------
98+
99+
syntax UnitTestSkip is statement
100+
"skip" "test"
101+
begin
102+
MCUnitTestSkip()
103+
end syntax
104+
105+
public handler MCUnitTestSkip()
106+
MCUnitOutputTest(true, "", "SKIP", "")
107+
end handler
108+
109+
syntax UnitTestSkipDescription is statement
110+
"skip" "test" <Description: Expression>
111+
begin
112+
MCUnitTestSkipDescription(Description)
113+
end syntax
114+
115+
public handler MCUnitTestSkipDescription(in pDescription as string)
116+
MCUnitOutputTest(true, pDescription, "SKIP", "")
117+
end handler
118+
119+
syntax UnitTestSkipReason is statement
120+
"skip" "test" "because" <Reason: Expression>
121+
begin
122+
MCUnitTestSkipReason(Reason)
123+
end syntax
124+
125+
public handler MCUnitTestSkipReason(in pReason as string)
126+
MCUnitOutputTest(true, "", "SKIP", pReason)
127+
end handler
128+
129+
syntax UnitTestSkipDescriptionAndReason is statement
130+
"skip" "test" <Description: Expression> "because" <Reason: Expression>
131+
begin
132+
MCUnitTestSkipDescriptionAndReason(Description, Reason)
133+
end syntax
134+
135+
public handler MCUnitTestSkipDescriptionAndReason(in pDescription as string, in pReason as string)
136+
MCUnitOutputTest(true, pDescription, "SKIP", pReason)
137+
end handler
138+
139+
----------------------------------------------------------------
140+
141+
syntax UnitTestFails is statement
142+
"test" "fails" "when" <Condition: Expression>
143+
begin
144+
MCUnitTestFails(Condition)
145+
end syntax
146+
147+
public handler MCUnitTestFails(in pCondition as boolean)
148+
MCUnitOutputTest(pCondition, "", "TODO", "")
149+
end handler
150+
151+
syntax UnitTestFailsDescription is statement
152+
"test" <Description: Expression> "fails" "when" <Condition: Expression>
153+
begin
154+
MCUnitTestFailsDescription(Description, Condition)
155+
end syntax
156+
157+
public handler MCUnitTestFailsDescription(in pDescription as string, in pCondition as boolean)
158+
MCUnitOutputTest(pCondition, pDescription, "TODO", "")
159+
end handler
160+
161+
syntax UnitTestFailsReason is statement
162+
"test" "fails" "when" <Condition: Expression> "because" <Reason: Expression>
163+
begin
164+
MCUnitTestFailsReason(Condition, Reason)
165+
end syntax
166+
167+
public handler MCUnitTestFailsReason(in pCondition as boolean, in pReason as string)
168+
MCUnitOutputTest(pCondition, "", "TODO", pReason)
169+
end handler
170+
171+
syntax UnitTestFailsDescriptionAndReason is statement
172+
"test" <Description: Expression> "fails" "when" <Condition: Expression> "because" <Reason: Expression>
173+
begin
174+
MCUnitTestFailsDescriptionAndReason(Description, Condition, Reason)
175+
end syntax
176+
177+
public handler MCUnitTestFailsDescriptionAndReason(in pDescription as string, in pCondition as boolean, in pReason as string)
178+
MCUnitOutputTest(pCondition, pDescription, "TODO", pReason)
179+
end handler
180+
181+
----------------------------------------------------------------
182+
-- Main functions used by tests
183+
----------------------------------------------------------------
184+
185+
public handler MCUnitPlan(in pCount as number)
186+
variable tNumberString as string
187+
put pCount formatted as string into tNumberString
188+
189+
-- We have to do a bit of a dance to format strings correctly
190+
-- at the moment
191+
if "." is in tNumberString then
192+
variable tDotOffset
193+
put the first offset of "." in tNumberString into tDotOffset
194+
delete char tDotOffset to (the number of chars in tNumberString) of tNumberString
195+
end if
196+
197+
MCUnitOutput("1.." & tNumberString)
198+
end handler
199+
200+
public handler MCUnitDiagnostic(in pMessage as string)
201+
MCUnitOutput("#" && pMessage)
202+
end handler
203+
204+
----------------------------------------------------------------
205+
-- Helper functions for outputting test results
206+
----------------------------------------------------------------
207+
208+
handler MCUnitOutputTest(in pCondition as boolean, in pDescription as string, in pDirective as string, in pReason as string)
209+
variable tMessage as string
210+
211+
if pCondition then
212+
put "ok" into tMessage
213+
else
214+
put "not ok" into tMessage
215+
end if
216+
217+
if pDescription is not empty then
218+
put " " & pDescription after tMessage
219+
end if
220+
221+
if pDirective is not empty then
222+
put " #" && pDirective after tMessage
223+
224+
if pReason is not empty then
225+
put " " & pReason after tMessage
226+
end if
227+
end if
228+
229+
MCUnitOutput(tMessage)
230+
end handler
231+
232+
foreign handler MCStringEncode(in String as string, in Encoding as int, in IsExternalRep as bool, out Encoded as data) as bool binds to "<builtin>"
233+
234+
handler MCUnitOutput(in pMessage as string)
235+
variable tEncoded as data
236+
237+
if the last char of pMessage is not "\n" then
238+
put "\n" after pMessage
239+
end if
240+
241+
-- Encode as UTF-8, always.
242+
-- FIXME this should use LCB encoding library rather than directly
243+
-- calling into libfoundation
244+
MCStringEncode(pMessage, 4 /* UTF-8 */, false, tEncoded)
245+
246+
write tEncoded to stream the output stream
247+
end handler
248+
249+
end module

toolchain/lc-compile/src/module-helper.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ extern builtin_module_descriptor __com_livecode_string_module_info;
4646
extern builtin_module_descriptor __com_livecode_system_module_info;
4747
extern builtin_module_descriptor __com_livecode_type_module_info;
4848
extern builtin_module_descriptor __com_livecode_typeconvert_module_info;
49+
extern builtin_module_descriptor __com_livecode_unittest_module_info;
4950

5051
builtin_module_descriptor* g_builtin_modules[] =
5152
{
@@ -70,7 +71,8 @@ builtin_module_descriptor* g_builtin_modules[] =
7071
&__com_livecode_string_module_info,
7172
&__com_livecode_system_module_info,
7273
&__com_livecode_type_module_info,
73-
&__com_livecode_typeconvert_module_info
74+
&__com_livecode_typeconvert_module_info,
75+
&__com_livecode_unittest_module_info,
7476
};
7577

7678
unsigned int g_builtin_module_count = sizeof(g_builtin_modules) / sizeof(builtin_module_descriptor*);

0 commit comments

Comments
 (0)