├── .gitignore ├── Makefile ├── README.md ├── doc ├── index.html └── ldoc.css ├── rockspecs ├── telescope-0.4.0-1.rockspec ├── telescope-0.4.1-1.rockspec ├── telescope-0.6.0-1.rockspec └── telescope-scm-1.rockspec ├── spec ├── fixtures │ └── syntax.lua └── telescope_spec.lua ├── telescope.lua ├── telescope └── compat_env.lua └── tsc /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .*.swp 3 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: test spec 2 | LUA_DIR = /usr/local 3 | LUA_VERSION = `lua -e 'print(_VERSION:sub(5,7))'` 4 | LUA_SHARE = $(LUA_DIR)/share/lua/$(LUA_VERSION) 5 | 6 | .PHONY : test clean docs install uninstall 7 | 8 | spec: 9 | @./tsc -f spec/*.lua 10 | 11 | test: 12 | @./tsc spec/*.lua 13 | 14 | docs: clean 15 | ldoc -t "Telescope API Docs" telescope.lua 16 | 17 | clean: 18 | rm -rf docs 19 | 20 | install: 21 | @mkdir -p $(LUA_SHARE)/telescope 22 | cp telescope.lua $(LUA_SHARE) 23 | cp telescope/compat_env.lua $(LUA_SHARE)/telescope 24 | cp tsc $(LUA_DIR)/bin 25 | 26 | uninstall: 27 | -rm $(LUA_SHARE)/telescope.lua 28 | -rm -rf $(LUA_SHARE)/telescope 29 | -rm $(LUA_DIR)/bin/tsc 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Telescope 2 | 3 | Telescope is a highly customizable test library for Lua that allows for 4 | declarative tests with nested contexts. 5 | 6 | ## Features 7 | 8 | * Compatible with Lua 5.1 and 5.2. 9 | * Nestable test contexts/descriptions. 10 | * [BDD](http://en.wikipedia.org/wiki/Behavior_Driven_Development)-style spec names. 11 | * Before/after functions per context. 12 | * Integrated code coverage reports using [Luacov](http://luacov.luaforge.net/). 13 | * You can easily add your own assertions. 14 | * Many different formatting options for tests and reports. 15 | * Simple, well documented API makes it easy to extend/hack. 16 | * Command line runner allows you to input Lua snippet callbacks, so you can, for example, 17 | drop to a debugger on failed tests, or wrap test calls around a profiler, etc. 18 | 19 | ## An Example 20 | 21 | context("A context", function() 22 | before(function() end) 23 | after(function() end) 24 | context("A nested context", function() 25 | test("A test", function() 26 | assert_not_equal("ham", "cheese") 27 | end) 28 | context("Another nested context", function() 29 | test("Another test", function() 30 | assert_greater_than(2, 1) 31 | end) 32 | end) 33 | end) 34 | test("A test in the top-level context", function() 35 | assert_equal(3, 1) 36 | end) 37 | end) 38 | 39 | ## Getting it 40 | 41 | You can install Telescope using Luarocks: 42 | 43 | sudo luarocks install telescope 44 | 45 | You can also check out the source code from Git, and install via "make" if you 46 | prefer: 47 | 48 | git clone git://github.com/norman/telescope.git 49 | cd telescope 50 | make install 51 | 52 | ## Running your tests 53 | 54 | Telescope comes with a command-line test runner named `tsc`. Simply run: 55 | 56 | tsc my_test_file.lua 57 | 58 | Or perhaps 59 | 60 | tsc -f test/*.lua 61 | 62 | The full test output (what you get using "-f") from the examples given would be: 63 | 64 | ------------------------------------------------------------------------ 65 | A context: 66 | A nested context: 67 | A test [P] 68 | Another nested context: 69 | Another test [P] 70 | A test in the top-level context [F] 71 | ------------------------------------------------------------------------ 72 | A test with no context [U] 73 | Another test with no context [U] 74 | ------------------------------------------------------------------------ 75 | This is a context: 76 | This is another context: 77 | this is a test [U] 78 | this is another test [U] 79 | this is another test [U] 80 | ------------------------------------------------------------------------ 81 | 8 tests 2 passed 3 assertions 1 failed 0 errors 5 unassertive 0 pending 82 | 83 | A test in the top-level context: 84 | Assert failed: expected '3' to be equal to '1' 85 | stack traceback: 86 | ...ib/luarocks/rocks//telescope/scm-1/lua/telescope.lua:139: in function 'assert_equal' 87 | example.lua:18: in function 88 | [C]: in function 'pcall' 89 | ...ib/luarocks/rocks//telescope/scm-1/lua/telescope.lua:330: in function 'invoke_test' 90 | ...ib/luarocks/rocks//telescope/scm-1/lua/telescope.lua:362: in function 'run' 91 | ...usr/local/lib/luarocks/rocks//telescope/scm-1/bin/ts:147: in main chunk 92 | [C]: ? 93 | 94 | Telescope tells you which tests were run, how many assertions they called, 95 | how many passed, how many failed, how many produced errors, how many provided 96 | a name but no implementation, and how many didn't assert anything. In the event 97 | of any failures or errors, it shows you stack traces. 98 | 99 | You can customize the test output to be as verbose or silent as you want, and 100 | easily write your own test reporters - the source is well documented. 101 | 102 | You can pass in snippets of Lua code on the command line to run as callbacks 103 | for various test success/failure scenarios, and easily customize the output or 104 | use Telescope with other applications. 105 | 106 | You can see all the available command-line options, and some examples by running: 107 | 108 | tsc -h 109 | 110 | ### More Examples 111 | 112 | -- Tests can be outside of contexts, if you want 113 | test("A test with no context", function() 114 | end) 115 | 116 | test("Another test with no context", function() 117 | end) 118 | 119 | -- Contexts and tests with various aliases 120 | spec("This is a context", function() 121 | describe("This is another context", function() 122 | it("this is a test", function() 123 | end) 124 | expect("this is another test", function() 125 | end) 126 | should("this is another test", function() 127 | end) 128 | end) 129 | end) 130 | 131 | ### Even More Examples 132 | 133 | -- change the name of your test or context blocks if you want something 134 | -- different 135 | telescope.context_aliases = {"specify"} 136 | telescope.test_aliases = {"verify"} 137 | 138 | -- create your own assertions 139 | telescope.make_assertion("longer_than", "%s to be longer than %s chars", 140 | function(a, b) return string.len(a) > b end) 141 | -- creates two assertions: assert_longer_than and assert_not_longer_than, 142 | -- which give error messages such as: 143 | -- Assertion error: expected "hello world" to be longer than 25 chars 144 | -- Assertion error: expected "hello world" not to be longer than 2 chars 145 | 146 | -- create a test runner with callbacks to show progress and 147 | -- drop to a debugger on errors 148 | local contexts = telescope.load_contexts(file) 149 | local results = telescope.run(contexts, { 150 | after = function(t) io.stdout:write(t.status_label) end, 151 | error = function(t) debug.debug() end 152 | }) 153 | 154 | -- call "tsc" on the command line with a callback to generate a custom report 155 | tsc --after="function(t) print(t.status_label, t.name, t.context) end" example.lua 156 | 157 | ## Author 158 | 159 | [Norman Clarke](mailto:norman@njclarke.com) 160 | 161 | Please feel free to email me bug reports or feature requests. 162 | 163 | ## Acknowledgements 164 | 165 | Telescope's initial beta release was made on Aug 25, 2009 - the 400th 166 | anniversary of the invention of the telescope. 167 | 168 | Thanks to [ScrewUnit](http://github.com/nathansobo/screw-unit/tree/master), 169 | [Contest](http://github.com/citrusbyte/contest) and 170 | [Luaspec](http://github.com/mirven/luaspec/) for inspiration. 171 | 172 | Thanks to [Eric Knudtson](http://twitter.com/vikingux) for helping me come up 173 | with the name "Telescope." 174 | 175 | ## License ## 176 | 177 | The MIT License 178 | 179 | Copyright (c) 2009-2012 [Norman Clarke](mailto:norman@njclarke.com) 180 | 181 | Permission is hereby granted, free of charge, to any person obtaining a copy of 182 | this software and associated documentation files (the "Software"), to deal in 183 | the Software without restriction, including without limitation the rights to 184 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 185 | of the Software, and to permit persons to whom the Software is furnished to do 186 | so, subject to the following conditions: 187 | 188 | The above copyright notice and this permission notice shall be included in all 189 | copies or substantial portions of the Software. 190 | 191 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 192 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 193 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 194 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 195 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 196 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 197 | SOFTWARE. 198 | -------------------------------------------------------------------------------- /doc/index.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | Telescope API Docs 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 |
16 |
17 |
18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 43 | 44 |
45 | 46 |

Module telescope

47 | 48 |

Telescope is a test library for Lua that allows for flexible, declarative 49 | tests.

50 |

The documentation produced here is intended largely for developers 51 | working on Telescope. For information on using Telescope, please visit the 52 | project homepage at: http://github.com/norman/telescope#readme.

53 |

Info:

54 |
    55 |
  • Release: 0.6
  • 56 |
57 | 58 | 59 |

Functions

60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 |
make_assertion (name, message, func)Create a custom assertion.
load_contexts (contexts)Build a contexts table from the test file or function given in target.
run (contexts, callbacks, test_filter)Run all tests.
test_report (contexts, results)Return a detailed report for each context, with the status of each test.
error_report (contexts, results)Return a table of stack traces for tests which produced a failure or an error.
summary_report (contexts, results)Get a one-line report and a summary table with the status counts.
86 |

Tables

87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 |
status_codesThe status codes that can be returned by an invoked test.
status_labelsLabels used to show the various status_codes as a single character.
context_aliasesThe default names for context blocks.
test_aliasesThe default names for test blocks.
before_aliasesThe default names for "before" blocks.
after_aliasesThe default names for "after" blocks.
assertionsThe default assertions.
117 | 118 |
119 |
120 | 121 | 122 |

Functions

123 |
124 |
125 | 126 | make_assertion (name, message, func) 127 |
128 |
129 | Create a custom assertion. 130 | This creates an assertion along with a corresponding negative assertion. It 131 | is used internally by telescope to create the default assertions. 132 | 133 |

Parameters:

134 |
    135 |
  • name 136 | The base name of the assertion. 137 |

    138 | The name will be used as the basis of the positive and negative assertions; 139 | i.e., the name equal would be used to create the assertions 140 | assert_equal and assert_not_equal. 141 |

  • 142 |
  • message 143 | The base message that will be shown. 144 |

    145 | The assertion message is what is shown when the assertion fails. It will be 146 | prefixed with the string in telescope.assertion_message_prefix. 147 | The variables passed to telescope.make_assertion are interpolated 148 | in the message string using string.format. When creating the 149 | inverse assertion, the message is reused, with " to be " replaced 150 | by " not to be ". Hence a recommended format is something like: 151 | "%s to be similar to %s". 152 |

  • 153 |
  • func 154 | The assertion function itself. 155 |

    156 | The assertion function can have any number of arguments. 157 |

  • 158 |
159 | 160 | 161 | 162 | 163 |

Usage:

164 |
    165 |
    <tt>make_assertion("equal", "%s to be equal to %s", function(a, b)
    166 |  return a == b end)</tt>
    167 |
168 | 169 |
170 |
171 | 172 | load_contexts (contexts) 173 |
174 |
175 | Build a contexts table from the test file or function given in target. 176 | If the optional contexts table argument is provided, then the 177 | resulting contexts will be added to it. 178 |

179 | The resulting contexts table's structure is as follows: 180 |

181 | 182 | { 183 | {parent = 0, name = "this is a context", context = true}, 184 | {parent = 1, name = "this is a nested context", context = true}, 185 | {parent = 2, name = "this is a test", test = function}, 186 | {parent = 2, name = "this is another test", test = function}, 187 | {parent = 0, name = "this is test outside any context", test = function}, 188 | } 189 | 190 | 191 |

Parameters:

192 |
    193 |
  • contexts 194 | A optional table in which to collect the resulting contexts 195 | and function.
  • 196 |
197 | 198 | 199 | 200 | 201 | 202 |
203 |
204 | 205 | run (contexts, callbacks, test_filter) 206 |
207 |
208 | Run all tests. 209 | This function will exectute each function in the contexts table. 210 | 211 |

Parameters:

212 |
    213 |
  • contexts 214 | The contexts created by load_contexts.
  • 215 |
  • callbacks 216 | A table of callback functions to be invoked before or after 217 | various test states. 218 |

    219 | There is a callback for each test status_code, and callbacks to run 220 | before or after each test invocation regardless of outcome. 221 |

    222 |
      223 |
    • after - will be invoked after each test
    • 224 |
    • before - will be invoked before each test
    • 225 |
    • err - will be invoked after each test which results in an error
    • 226 |
    • fail - will be invoked after each failing test
    • 227 |
    • pass - will be invoked after each passing test
    • 228 |
    • pending - will be invoked after each pending test
    • 229 |
    • unassertive - will be invoked after each test which doesn't assert 230 | anything
    • 231 |
    232 |

    233 | Callbacks can be used, for example, to drop into a debugger upon a failed 234 | assertion or error, for profiling, or updating a GUI progress meter. 235 |

  • 236 |
  • test_filter 237 | A function to filter tests that match only conditions that you specify. 238 |

    239 | For example, the folling would allow you to run only tests whose name matches a pattern: 240 |

    241 |

    242 | 243 | function(t) return t.name:match("%s* lexer") end 244 | 245 |

  • 246 |
247 | 248 |

Returns:

249 |
    250 | 251 | A table of result tables. Each result table has the following 252 | fields: 253 |
      254 |
    • assertions_invoked - the number of assertions the test invoked
    • 255 |
    • context - the name of the context
    • 256 |
    • message - a table with an error message and stack trace
    • 257 |
    • name - the name of the test
    • 258 |
    • status_code - the resulting status code
    • 259 |
    • status_label - the label for the status_code
    • 260 |
    261 |
262 | 263 | 264 |

see also:

265 | 269 | 270 | 271 |
272 |
273 | 274 | test_report (contexts, results) 275 |
276 |
277 | Return a detailed report for each context, with the status of each test. 278 | 279 |

Parameters:

280 |
    281 |
  • contexts 282 | The contexts returned by load_contexts.
  • 283 |
  • results 284 | The results returned by run.
  • 285 |
286 | 287 | 288 | 289 | 290 | 291 |
292 |
293 | 294 | error_report (contexts, results) 295 |
296 |
297 | Return a table of stack traces for tests which produced a failure or an error. 298 | 299 |

Parameters:

300 |
    301 |
  • contexts 302 | The contexts returned by load_contexts.
  • 303 |
  • results 304 | The results returned by run.
  • 305 |
306 | 307 | 308 | 309 | 310 | 311 |
312 |
313 | 314 | summary_report (contexts, results) 315 |
316 |
317 | Get a one-line report and a summary table with the status counts. The 318 | counts given are: total tests, assertions, passed tests, failed tests, 319 | pending tests, and tests which didn't assert anything. 320 | 321 |

Parameters:

322 |
    323 |
  • contexts 324 | The contexts returned by load_contexts.
  • 325 |
  • results 326 | The results returned by run.
  • 327 |
328 | 329 |

Returns:

330 |
    331 |
  1. 332 | A report that can be printed
  2. 333 |
  3. 334 | A table with the various counts. Its fields are: 335 | assertions, errors, failed, passed, 336 | pending, tests, unassertive.
  4. 337 |
338 | 339 | 340 | 341 | 342 |
343 |
344 |

Tables

345 |
346 |
347 | 348 | status_codes 349 |
350 |
351 | The status codes that can be returned by an invoked test. These should not be overidden. 352 | 353 |

Fields:

354 |
    355 |
  • err 356 | - This is returned when an invoked test results in an error 357 | rather than a passed or failed assertion.
  • 358 |
  • fail 359 | - This is returned when an invoked test contains one or more failing assertions.
  • 360 |
  • pass 361 | - This is returned when all of a test's assertions pass.
  • 362 |
  • pending 363 | - This is returned when a test does not have a corresponding function.
  • 364 |
  • unassertive 365 | - This is returned when an invoked test does not produce 366 | errors, but does not contain any assertions.
  • 367 |
368 | 369 | 370 | 371 | 372 | 373 |
374 |
375 | 376 | status_labels 377 |
378 |
379 | Labels used to show the various status_codes as a single character. 380 | These can be overidden if you wish. 381 | 382 | 383 | 384 | 385 |

see also:

386 | 389 | 390 | 391 |
392 |
393 | 394 | context_aliases 395 |
396 |
397 | The default names for context blocks. It defaults to "context", "spec" and 398 | "describe." 399 | 400 |

Fields:

401 |
    402 |
  • context 403 |
  • 404 |
  • describe 405 |
  • 406 |
  • spec 407 |
  • 408 |
409 | 410 | 411 | 412 | 413 | 414 |
415 |
416 | 417 | test_aliases 418 |
419 |
420 | The default names for test blocks. It defaults to "test," "it", "expect", 421 | "they" and "should." 422 | 423 |

Fields:

424 |
    425 |
  • test 426 |
  • 427 |
  • it 428 |
  • 429 |
  • expect 430 |
  • 431 |
  • should 432 |
  • 433 |
  • they 434 |
  • 435 |
436 | 437 | 438 | 439 | 440 | 441 |
442 |
443 | 444 | before_aliases 445 |
446 |
447 | The default names for "before" blocks. It defaults to "before" and "setup." 448 | The function in the before block will be run before each sibling test function 449 | or context. 450 | 451 |

Fields:

452 |
    453 |
  • before 454 |
  • 455 |
  • setup 456 |
  • 457 |
458 | 459 | 460 | 461 | 462 | 463 |
464 |
465 | 466 | after_aliases 467 |
468 |
469 | The default names for "after" blocks. It defaults to "after" and "teardown." 470 | The function in the after block will be run after each sibling test function 471 | or context. 472 | 473 |

Fields:

474 |
    475 |
  • after 476 |
  • 477 |
  • teardown 478 |
  • 479 |
480 | 481 | 482 | 483 | 484 | 485 |
486 |
487 | 488 | assertions 489 |
490 |
491 | The default assertions. 492 | These are the assertions built into telescope. You can override them or 493 | create your own custom assertions using make_assertion. 494 |
    495 |
  • assert_blank(a) - true if a is nil, or the empty string
  • 496 |
  • assert_empty(a) - true if a is an empty table
  • 497 |
  • assert_equal(a, b) - true if a == b
  • 498 |
  • assert_error(f) - true if function f produces an error
  • 499 |
  • assert_false(a) - true if a is false
  • 500 |
  • assert_greater_than(a, b) - true if a > b
  • 501 |
  • assert_gte(a, b) - true if a >= b
  • 502 |
  • assert_less_than(a, b) - true if a < b
  • 503 |
  • assert_lte(a, b) - true if a <= b
  • 504 |
  • assert_match(a, b) - true if b is a string that matches pattern a
  • 505 |
  • assert_nil(a) - true if a is nil
  • 506 |
  • assert_true(a) - true if a is true
  • 507 |
  • assert_type(a, b) - true if a is of type b
  • 508 |
  • assert_not_blank(a) - true if a is not nil and a is not the empty string
  • 509 |
  • assert_not_empty(a) - true if a is a table, and a is not empty
  • 510 |
  • assert_not_equal(a, b) - true if a ~= b
  • 511 |
  • assert_not_error(f) - true if function f does not produce an error
  • 512 |
  • assert_not_false(a) - true if a is not false
  • 513 |
  • assert_not_greater_than(a, b) - true if not (a > b)
  • 514 |
  • assert_not_gte(a, b) - true if not (a >= b)
  • 515 |
  • assert_not_less_than(a, b) - true if not (a < b)
  • 516 |
  • assert_not_lte(a, b) - true if not (a <= b)
  • 517 |
  • assert_not_match(a, b) - true if the string b does not match the pattern a
  • 518 |
  • assert_not_nil(a) - true if a is not nil
  • 519 |
  • assert_not_true(a) - true if a is not true
  • 520 |
  • assert_not_type(a, b) - true if a is not of type b
  • 521 |
522 | 523 | 524 | 525 | 526 |

see also:

527 | 530 | 531 | 532 |
533 |
534 | 535 | 536 |
537 |
538 |
539 | generated by LDoc 1.3.12 540 |
541 |
542 | 543 | 544 | -------------------------------------------------------------------------------- /doc/ldoc.css: -------------------------------------------------------------------------------- 1 | /* BEGIN RESET 2 | 3 | Copyright (c) 2010, Yahoo! Inc. All rights reserved. 4 | Code licensed under the BSD License: 5 | http://developer.yahoo.com/yui/license.html 6 | version: 2.8.2r1 7 | */ 8 | html { 9 | color: #000; 10 | background: #FFF; 11 | } 12 | body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,button,textarea,p,blockquote,th,td { 13 | margin: 0; 14 | padding: 0; 15 | } 16 | table { 17 | border-collapse: collapse; 18 | border-spacing: 0; 19 | } 20 | fieldset,img { 21 | border: 0; 22 | } 23 | address,caption,cite,code,dfn,em,strong,th,var,optgroup { 24 | font-style: inherit; 25 | font-weight: inherit; 26 | } 27 | del,ins { 28 | text-decoration: none; 29 | } 30 | li { 31 | list-style: bullet; 32 | margin-left: 20px; 33 | } 34 | caption,th { 35 | text-align: left; 36 | } 37 | h1,h2,h3,h4,h5,h6 { 38 | font-size: 100%; 39 | font-weight: bold; 40 | } 41 | q:before,q:after { 42 | content: ''; 43 | } 44 | abbr,acronym { 45 | border: 0; 46 | font-variant: normal; 47 | } 48 | sup { 49 | vertical-align: baseline; 50 | } 51 | sub { 52 | vertical-align: baseline; 53 | } 54 | legend { 55 | color: #000; 56 | } 57 | input,button,textarea,select,optgroup,option { 58 | font-family: inherit; 59 | font-size: inherit; 60 | font-style: inherit; 61 | font-weight: inherit; 62 | } 63 | input,button,textarea,select {*font-size:100%; 64 | } 65 | /* END RESET */ 66 | 67 | body { 68 | margin-left: 1em; 69 | margin-right: 1em; 70 | font-family: arial, helvetica, geneva, sans-serif; 71 | background-color: #ffffff; margin: 0px; 72 | } 73 | 74 | code, tt { font-family: monospace; } 75 | span.parameter { font-family:monospace; } 76 | span.parameter:after { content:":"; } 77 | span.types:before { content:"("; } 78 | span.types:after { content:")"; } 79 | .type { font-weight: bold; font-style:italic } 80 | 81 | body, p, td, th { font-size: .95em; line-height: 1.2em;} 82 | 83 | p, ul { margin: 10px 0 0 0px;} 84 | 85 | strong { font-weight: bold;} 86 | 87 | em { font-style: italic;} 88 | 89 | h1 { 90 | font-size: 1.5em; 91 | margin: 0 0 20px 0; 92 | } 93 | h2, h3, h4 { margin: 15px 0 10px 0; } 94 | h2 { font-size: 1.25em; } 95 | h3 { font-size: 1.15em; } 96 | h4 { font-size: 1.06em; } 97 | 98 | a:link { font-weight: bold; color: #004080; text-decoration: none; } 99 | a:visited { font-weight: bold; color: #006699; text-decoration: none; } 100 | a:link:hover { text-decoration: underline; } 101 | 102 | hr { 103 | color:#cccccc; 104 | background: #00007f; 105 | height: 1px; 106 | } 107 | 108 | blockquote { margin-left: 3em; } 109 | 110 | ul { list-style-type: disc; } 111 | 112 | p.name { 113 | font-family: "Andale Mono", monospace; 114 | padding-top: 1em; 115 | } 116 | 117 | pre.example { 118 | background-color: rgb(245, 245, 245); 119 | border: 1px solid silver; 120 | padding: 10px; 121 | margin: 10px 0 10px 0; 122 | font-family: "Andale Mono", monospace; 123 | font-size: .85em; 124 | } 125 | 126 | pre { 127 | background-color: rgb(245, 245, 245); 128 | border: 1px solid silver; 129 | padding: 10px; 130 | margin: 10px 0 10px 0; 131 | overflow: auto; 132 | font-family: "Andale Mono", monospace; 133 | } 134 | 135 | 136 | table.index { border: 1px #00007f; } 137 | table.index td { text-align: left; vertical-align: top; } 138 | 139 | #container { 140 | margin-left: 1em; 141 | margin-right: 1em; 142 | background-color: #f0f0f0; 143 | } 144 | 145 | #product { 146 | text-align: center; 147 | border-bottom: 1px solid #cccccc; 148 | background-color: #ffffff; 149 | } 150 | 151 | #product big { 152 | font-size: 2em; 153 | } 154 | 155 | #main { 156 | background-color: #f0f0f0; 157 | border-left: 2px solid #cccccc; 158 | } 159 | 160 | #navigation { 161 | float: left; 162 | width: 18em; 163 | vertical-align: top; 164 | background-color: #f0f0f0; 165 | overflow: visible; 166 | } 167 | 168 | #navigation h2 { 169 | background-color:#e7e7e7; 170 | font-size:1.1em; 171 | color:#000000; 172 | text-align: left; 173 | padding:0.2em; 174 | border-top:1px solid #dddddd; 175 | border-bottom:1px solid #dddddd; 176 | } 177 | 178 | #navigation ul 179 | { 180 | font-size:1em; 181 | list-style-type: none; 182 | margin: 1px 1px 10px 1px; 183 | } 184 | 185 | #navigation li { 186 | text-indent: -1em; 187 | display: block; 188 | margin: 3px 0px 0px 22px; 189 | } 190 | 191 | #navigation li li a { 192 | margin: 0px 3px 0px -1em; 193 | } 194 | 195 | #content { 196 | margin-left: 18em; 197 | padding: 1em; 198 | width: 700px; 199 | border-left: 2px solid #cccccc; 200 | border-right: 2px solid #cccccc; 201 | background-color: #ffffff; 202 | } 203 | 204 | #about { 205 | clear: both; 206 | padding: 5px; 207 | border-top: 2px solid #cccccc; 208 | background-color: #ffffff; 209 | } 210 | 211 | @media print { 212 | body { 213 | font: 12pt "Times New Roman", "TimeNR", Times, serif; 214 | } 215 | a { font-weight: bold; color: #004080; text-decoration: underline; } 216 | 217 | #main { 218 | background-color: #ffffff; 219 | border-left: 0px; 220 | } 221 | 222 | #container { 223 | margin-left: 2%; 224 | margin-right: 2%; 225 | background-color: #ffffff; 226 | } 227 | 228 | #content { 229 | padding: 1em; 230 | background-color: #ffffff; 231 | } 232 | 233 | #navigation { 234 | display: none; 235 | } 236 | pre.example { 237 | font-family: "Andale Mono", monospace; 238 | font-size: 10pt; 239 | page-break-inside: avoid; 240 | } 241 | } 242 | 243 | table.module_list { 244 | border-width: 1px; 245 | border-style: solid; 246 | border-color: #cccccc; 247 | border-collapse: collapse; 248 | } 249 | table.module_list td { 250 | border-width: 1px; 251 | padding: 3px; 252 | border-style: solid; 253 | border-color: #cccccc; 254 | } 255 | table.module_list td.name { background-color: #f0f0f0; ; min-width: 200px; } 256 | table.module_list td.summary { width: 100%; } 257 | 258 | 259 | table.function_list { 260 | border-width: 1px; 261 | border-style: solid; 262 | border-color: #cccccc; 263 | border-collapse: collapse; 264 | } 265 | table.function_list td { 266 | border-width: 1px; 267 | padding: 3px; 268 | border-style: solid; 269 | border-color: #cccccc; 270 | } 271 | table.function_list td.name { background-color: #f0f0f0; ; min-width: 200px; } 272 | table.function_list td.summary { width: 100%; } 273 | 274 | dl.table dt, dl.function dt {border-top: 1px solid #ccc; padding-top: 1em;} 275 | dl.table dd, dl.function dd {padding-bottom: 1em; margin: 10px 0 0 20px;} 276 | dl.table h3, dl.function h3 {font-size: .95em;} 277 | 278 | /* stop sublists from having initial vertical space */ 279 | ul ul { margin-top: 0px; } 280 | ol ul { margin-top: 0px; } 281 | ol ol { margin-top: 0px; } 282 | ul ol { margin-top: 0px; } 283 | 284 | /* styles for prettification of source */ 285 | pre .comment { color: #558817; } 286 | pre .constant { color: #a8660d; } 287 | pre .escape { color: #844631; } 288 | pre .keyword { color: #2239a8; font-weight: bold; } 289 | pre .library { color: #0e7c6b; } 290 | pre .marker { color: #512b1e; background: #fedc56; font-weight: bold; } 291 | pre .string { color: #a8660d; } 292 | pre .number { color: #f8660d; } 293 | pre .operator { color: #2239a8; font-weight: bold; } 294 | pre .preprocessor, pre .prepro { color: #a33243; } 295 | pre .global { color: #800080; } 296 | pre .prompt { color: #558817; } 297 | pre .url { color: #272fc2; text-decoration: underline; } 298 | -------------------------------------------------------------------------------- /rockspecs/telescope-0.4.0-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "telescope" 2 | version = "0.4.0-1" 3 | source = { 4 | url = "http://cloud.github.com/downloads/norman/telescope/telescope-0.4.0.tar.gz", 5 | md5 = "c69c6c99e2d9738bab1f3bd941831536" 6 | } 7 | description = { 8 | summary = "A test/spec library for Lua.", 9 | detailed = [[ 10 | Telescope is a test/spec library for Lua. 11 | ]], 12 | license = "MIT/X11", 13 | homepage = "http://telescope.luaforge.net" 14 | } 15 | dependencies = { 16 | "lua >= 5.1" 17 | } 18 | 19 | build = { 20 | type = "none", 21 | install = { 22 | lua = { 23 | "telescope.lua", 24 | }, 25 | bin = { 26 | "tsc" 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /rockspecs/telescope-0.4.1-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "telescope" 2 | version = "0.4.1-1" 3 | source = { 4 | url = "http://cloud.github.com/downloads/norman/telescope/telescope-0.4.1.tar.gz", 5 | md5 = "e240350716994873fe1ad7f67918c3b2" 6 | } 7 | description = { 8 | summary = "A test/spec library for Lua.", 9 | detailed = [[ 10 | Telescope is a test/spec library for Lua. 11 | ]], 12 | license = "MIT/X11", 13 | homepage = "http://telescope.luaforge.net" 14 | } 15 | dependencies = { 16 | "lua >= 5.1" 17 | } 18 | 19 | build = { 20 | type = "none", 21 | install = { 22 | lua = { 23 | "telescope.lua", 24 | }, 25 | bin = { 26 | "tsc" 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /rockspecs/telescope-0.6.0-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "telescope" 2 | version = "0.6.0-1" 3 | source = { 4 | url = "git://github.com/norman/telescope.git", 5 | tag = "0.6.0" 6 | } 7 | description = { 8 | summary = "A test/spec library for Lua.", 9 | detailed = [[ 10 | Telescope is a test/spec library for Lua. 11 | ]], 12 | license = "MIT/X11", 13 | homepage = "http://github.com/norman/telescope" 14 | } 15 | dependencies = { 16 | "lua >= 5.1" 17 | } 18 | 19 | build = { 20 | type = "none", 21 | install = { 22 | lua = { 23 | "telescope.lua", 24 | ["telescope.compat_env"] = "telescope/compat_env.lua" 25 | }, 26 | bin = { 27 | "tsc" 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /rockspecs/telescope-scm-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "telescope" 2 | version = "scm-1" 3 | source = { 4 | url = "git://github.com/norman/telescope.git", 5 | } 6 | description = { 7 | summary = "A test/spec library for Lua.", 8 | detailed = [[ 9 | Telescope is a test/spec library for Lua. 10 | ]], 11 | license = "MIT/X11", 12 | homepage = "http://telescope.luaforge.net" 13 | } 14 | dependencies = { 15 | "lua >= 5.1" 16 | } 17 | 18 | build = { 19 | type = "none", 20 | install = { 21 | lua = { 22 | "telescope.lua", 23 | ["telescope.compat_env"] = "telescope/compat_env.lua", 24 | }, 25 | bin = { 26 | "tsc" 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /spec/fixtures/syntax.lua: -------------------------------------------------------------------------------- 1 | require 'telescope' 2 | 3 | local i = 0 4 | 5 | context("A context", function() 6 | 7 | before(function() i = i + 1 end) 8 | after(function() i = i - 1 end) 9 | 10 | context("A nested context", function() 11 | 12 | test("A passing test", function() 13 | assert_true(true) 14 | end) 15 | 16 | test("A failing test", function() 17 | assert_true(false) 18 | end) 19 | 20 | test("An unassertive test", function() 21 | local hello = "world" 22 | end) 23 | 24 | test("A test that causes an error", function() 25 | t.hello = "world" 26 | end) 27 | 28 | test("A pending test") 29 | 30 | context("A deeply nested context", function() 31 | end) 32 | 33 | end) 34 | 35 | end) 36 | 37 | test("A test in the top level") 38 | -------------------------------------------------------------------------------- /spec/telescope_spec.lua: -------------------------------------------------------------------------------- 1 | local telescope = require "telescope" 2 | 3 | describe("The Telescope Test Framework", function() 4 | 5 | local contexts 6 | 7 | context("The Telescope module", function() 8 | it("should have a 'version' member", function() 9 | assert_equal("string", type(telescope.version)) 10 | end) 11 | it("should have a '_VERSION' member", function() 12 | assert_equal("string", type(telescope._VERSION)) 13 | end) 14 | end) 15 | 16 | context("Telescope's syntax", function() 17 | 18 | before(function() 19 | contexts = telescope.load_contexts("spec/fixtures/syntax.lua") 20 | end) 21 | 22 | context("contexts and tests", function() 23 | 24 | it("should have names", function() 25 | assert_equal("A context", contexts[1].name) 26 | assert_equal("A passing test", contexts[3].name) 27 | end) 28 | 29 | it("should have parents", function() 30 | for i, c in ipairs(contexts) do 31 | assert_gte(c.parent, 0) 32 | end 33 | end) 34 | 35 | it("should have a parent of 0 when at the top level", function() 36 | assert_equal("A context", contexts[1].name) 37 | assert_equal(0, contexts[1].parent) 38 | assert_equal("A test in the top level", contexts[9].name) 39 | assert_equal(0, contexts[9].parent) 40 | end) 41 | 42 | end) 43 | 44 | context("contexts", function() 45 | 46 | it("can have contexts as children", function() 47 | assert_equal("A nested context", contexts[2].name) 48 | assert_equal(1, contexts[2].parent) 49 | end) 50 | 51 | it("can have tests as children", function() 52 | assert_equal("A nested context", contexts[3].context_name) 53 | assert_equal("A passing test", contexts[3].name) 54 | end) 55 | 56 | it("can have a 'before' function", function() 57 | assert_type(contexts[1].before, "function") 58 | end) 59 | 60 | it("can have an 'after' function", function() 61 | assert_type(contexts[1].after, "function") 62 | end) 63 | 64 | end) 65 | 66 | context("tests", function() 67 | 68 | it("when pending, should have true for the 'test' field", function() 69 | assert_equal("A pending test", contexts[7].name) 70 | assert_true(contexts[7].test) 71 | end) 72 | 73 | it("when non-pending, should have a function for the 'test' field", function() 74 | assert_equal("A test that causes an error", contexts[6].name) 75 | assert_equal("function", type(contexts[6].test)) 76 | end) 77 | 78 | end) 79 | 80 | context("load_context", function() 81 | 82 | it("should accept a function or a path to a module", function() 83 | func, err = assert(loadfile("spec/fixtures/syntax.lua")) 84 | contexts = telescope.load_contexts(func) 85 | -- We don't need to validate the entire thing, that's done in Syntax. 86 | -- Just make sure that the result is a context. 87 | assert_equal("A context", contexts[1].name) 88 | assert_equal("A passing test", contexts[3].name) 89 | end) 90 | 91 | end) 92 | 93 | end) 94 | 95 | end) 96 | -------------------------------------------------------------------------------- /telescope.lua: -------------------------------------------------------------------------------- 1 | --- Telescope is a test library for Lua that allows for flexible, declarative 2 | -- tests. The documentation produced here is intended largely for developers 3 | -- working on Telescope. For information on using Telescope, please visit the 4 | -- project homepage at: http://github.com/norman/telescope#readme. 5 | -- @release 0.6 6 | -- @class module 7 | -- @module 'telescope' 8 | local _M = {} 9 | 10 | local compat_env = require 'telescope.compat_env' 11 | 12 | local getfenv = _G.getfenv or compat_env.getfenv 13 | local setfenv = _G.setfenv or compat_env.setfenv 14 | 15 | 16 | local _VERSION = "0.6.0" 17 | 18 | --- The status codes that can be returned by an invoked test. These should not be overidden. 19 | -- @name status_codes 20 | -- @class table 21 | -- @field err - This is returned when an invoked test results in an error 22 | -- rather than a passed or failed assertion. 23 | -- @field fail - This is returned when an invoked test contains one or more failing assertions. 24 | -- @field pass - This is returned when all of a test's assertions pass. 25 | -- @field pending - This is returned when a test does not have a corresponding function. 26 | -- @field unassertive - This is returned when an invoked test does not produce 27 | -- errors, but does not contain any assertions. 28 | local status_codes = { 29 | err = 2, 30 | fail = 4, 31 | pass = 8, 32 | pending = 16, 33 | unassertive = 32 34 | } 35 | 36 | --- Labels used to show the various status_codes as a single character. 37 | -- These can be overidden if you wish. 38 | -- @name status_labels 39 | -- @class table 40 | -- @see status_codes 41 | -- @field status_codes.err 'E' 42 | -- @field status_codes.fail 'F' 43 | -- @field status_codes.pass 'P' 44 | -- @field status_codes.pending '?' 45 | -- @field status_codes.unassertive 'U' 46 | 47 | local status_labels = { 48 | [status_codes.err] = 'E', 49 | [status_codes.fail] = 'F', 50 | [status_codes.pass] = 'P', 51 | [status_codes.pending] = '?', 52 | [status_codes.unassertive] = 'U' 53 | } 54 | 55 | --- The default names for context blocks. It defaults to "context", "spec" and 56 | -- "describe." 57 | -- @name context_aliases 58 | -- @class table 59 | local context_aliases = {"context", "describe", "spec"} 60 | --- The default names for test blocks. It defaults to "test," "it", "expect", 61 | -- "they" and "should." 62 | -- @name test_aliases 63 | -- @class table 64 | local test_aliases = {"test", "it", "expect", "should", "they"} 65 | 66 | --- The default names for "before" blocks. It defaults to "before" and "setup." 67 | -- The function in the before block will be run before each sibling test function 68 | -- or context. 69 | -- @name before_aliases 70 | -- @class table 71 | local before_aliases = {"before", "setup"} 72 | 73 | --- The default names for "after" blocks. It defaults to "after" and "teardown." 74 | -- The function in the after block will be run after each sibling test function 75 | -- or context. 76 | -- @name after_aliases 77 | -- @class table 78 | local after_aliases = {"after", "teardown"} 79 | 80 | -- Prefix to place before all assertion messages. Used by make_assertion(). 81 | local assertion_message_prefix = "Assert failed: expected " 82 | 83 | --- The default assertions. 84 | -- These are the assertions built into telescope. You can override them or 85 | -- create your own custom assertions using make_assertion. 86 | -- 114 | -- @see make_assertion 115 | -- @name assertions 116 | -- @class table 117 | local assertions = {} 118 | 119 | --- Create a custom assertion. 120 | -- This creates an assertion along with a corresponding negative assertion. It 121 | -- is used internally by telescope to create the default assertions. 122 | -- @param name The base name of the assertion. 123 | --

124 | -- The name will be used as the basis of the positive and negative assertions; 125 | -- i.e., the name equal would be used to create the assertions 126 | -- assert_equal and assert_not_equal. 127 | --

128 | -- @param message The base message that will be shown. 129 | --

130 | -- The assertion message is what is shown when the assertion fails. It will be 131 | -- prefixed with the string in telescope.assertion_message_prefix. 132 | -- The variables passed to telescope.make_assertion are interpolated 133 | -- in the message string using string.format. When creating the 134 | -- inverse assertion, the message is reused, with " to be " replaced 135 | -- by " not to be ". Hence a recommended format is something like: 136 | -- "%s to be similar to %s". 137 | --

138 | -- @param func The assertion function itself. 139 | --

140 | -- The assertion function can have any number of arguments. 141 | --

142 | -- @usage make_assertion("equal", "%s to be equal to %s", function(a, b) 143 | -- return a == b end) 144 | -- @function make_assertion 145 | local function make_assertion(name, message, func) 146 | local num_vars = 0 147 | -- if the last vararg ends up nil, we'll need to pad the table with nils so 148 | -- that string.format gets the number of args it expects 149 | local format_message 150 | if type(message) == "function" then 151 | format_message = message 152 | else 153 | for _, _ in message:gmatch("%%s") do num_vars = num_vars + 1 end 154 | format_message = function(message, ...) 155 | local a = {} 156 | local args = {...} 157 | local nargs = select('#', ...) 158 | if nargs > num_vars then 159 | local userErrorMessage = args[num_vars+1] 160 | if type(userErrorMessage) == "string" then 161 | return(assertion_message_prefix .. userErrorMessage) 162 | else 163 | error(string.format('assert_%s expected %d arguments but got %d', name, num_vars, #args)) 164 | end 165 | end 166 | for i = 1, nargs do a[i] = tostring(v) end 167 | for i = nargs+1, num_vars do a[i] = 'nil' end 168 | return (assertion_message_prefix .. message):format(unpack(a)) 169 | end 170 | end 171 | 172 | assertions["assert_" .. name] = function(...) 173 | if assertion_callback then assertion_callback(...) end 174 | if not func(...) then 175 | error({format_message(message, ...), debug.traceback()}) 176 | end 177 | end 178 | end 179 | 180 | --- (local) Return a table with table t's values as keys and keys as values. 181 | -- @param t The table. 182 | local function invert_table(t) 183 | local t2 = {} 184 | for k, v in pairs(t) do t2[v] = k end 185 | return t2 186 | end 187 | 188 | -- (local) Truncate a string "s" to length "len", optionally followed by the 189 | -- string given in "after" if truncated; for example, truncate_string("hello 190 | -- world", 3, "...") 191 | -- @param s The string to truncate. 192 | -- @param len The desired length. 193 | -- @param after A string to append to s, if it is truncated. 194 | local function truncate_string(s, len, after) 195 | if #s <= len then 196 | return s 197 | else 198 | local s = s:sub(1, len):gsub("%s*$", '') 199 | if after then return s .. after else return s end 200 | end 201 | end 202 | 203 | --- (local) Filter a table's values by function. This function iterates over a 204 | -- table , returning only the table entries that, when passed into function f, 205 | -- yield a truthy value. 206 | -- @param t The table over which to iterate. 207 | -- @param f The filter function. 208 | local function filter(t, f) 209 | local a, b 210 | return function() 211 | repeat a, b = next(t, a) 212 | if not b then return end 213 | if f(a, b) then return a, b end 214 | until not b 215 | end 216 | end 217 | 218 | --- (local) Finds the value in the contexts table indexed with i, and returns a table 219 | -- of i's ancestor contexts. 220 | -- @param i The index in the contexts table to get ancestors for. 221 | -- @param contexts The table in which to find the ancestors. 222 | local function ancestors(i, contexts) 223 | if i == 0 then return end 224 | local a = {} 225 | local function func(j) 226 | if contexts[j].parent == 0 then return nil end 227 | table.insert(a, contexts[j].parent) 228 | func(contexts[j].parent) 229 | end 230 | func(i) 231 | return a 232 | end 233 | 234 | make_assertion("blank", "'%s' to be blank", function(a) return a == '' or a == nil end) 235 | make_assertion("empty", "'%s' to be an empty table", function(a) return not next(a) end) 236 | make_assertion("equal", "'%s' to be equal to '%s'", function(a, b) return a == b end) 237 | make_assertion("error", "result to be an error", function(f) return not pcall(f) end) 238 | make_assertion("false", "'%s' to be false", function(a) return a == false end) 239 | make_assertion("greater_than", "'%s' to be greater than '%s'", function(a, b) return a > b end) 240 | make_assertion("gte", "'%s' to be greater than or equal to '%s'", function(a, b) return a >= b end) 241 | make_assertion("less_than", "'%s' to be less than '%s'", function(a, b) return a < b end) 242 | make_assertion("lte", "'%s' to be less than or equal to '%s'", function(a, b) return a <= b end) 243 | make_assertion("match", "'%s' to be a match for %s", function(a, b) return (tostring(b)):match(a) end) 244 | make_assertion("nil", "'%s' to be nil", function(a) return a == nil end) 245 | make_assertion("true", "'%s' to be true", function(a) return a == true end) 246 | make_assertion("type", "'%s' to be a %s", function(a, b) return type(a) == b end) 247 | 248 | make_assertion("not_blank", "'%s' not to be blank", function(a) return a ~= '' and a ~= nil end) 249 | make_assertion("not_empty", "'%s' not to be an empty table", function(a) return not not next(a) end) 250 | make_assertion("not_equal", "'%s' not to be equal to '%s'", function(a, b) return a ~= b end) 251 | make_assertion("not_error", "result not to be an error", function(f) return not not pcall(f) end) 252 | make_assertion("not_match", "'%s' not to be a match for %s", function(a, b) return not (tostring(b)):match(a) end) 253 | make_assertion("not_nil", "'%s' not to be nil", function(a) return a ~= nil end) 254 | make_assertion("not_type", "'%s' not to be a %s", function(a, b) return type(a) ~= b end) 255 | 256 | --- Build a contexts table from the test file or function given in target. 257 | -- If the optional contexts table argument is provided, then the 258 | -- resulting contexts will be added to it. 259 | --

260 | -- The resulting contexts table's structure is as follows: 261 | --

262 | -- 263 | -- { 264 | -- {parent = 0, name = "this is a context", context = true}, 265 | -- {parent = 1, name = "this is a nested context", context = true}, 266 | -- {parent = 2, name = "this is a test", test = function}, 267 | -- {parent = 2, name = "this is another test", test = function}, 268 | -- {parent = 0, name = "this is test outside any context", test = function}, 269 | -- } 270 | -- 271 | -- @param contexts A optional table in which to collect the resulting contexts 272 | -- and function. 273 | -- @function load_contexts 274 | local function load_contexts(target, contexts) 275 | local env = {} 276 | local current_index = 0 277 | local context_table = contexts or {} 278 | 279 | local function context_block(name, func) 280 | table.insert(context_table, {parent = current_index, name = name, context = true}) 281 | local previous_index = current_index 282 | current_index = #context_table 283 | func() 284 | current_index = previous_index 285 | end 286 | 287 | local function test_block(name, func) 288 | local test_table = {name = name, parent = current_index, test = func or true} 289 | if current_index ~= 0 then 290 | test_table.context_name = context_table[current_index].name 291 | else 292 | test_table.context_name = 'top level' 293 | end 294 | table.insert(context_table, test_table) 295 | end 296 | 297 | local function before_block(func) 298 | context_table[current_index].before = func 299 | end 300 | 301 | local function after_block(func) 302 | context_table[current_index].after = func 303 | end 304 | 305 | for _, v in ipairs(after_aliases) do env[v] = after_block end 306 | for _, v in ipairs(before_aliases) do env[v] = before_block end 307 | for _, v in ipairs(context_aliases) do env[v] = context_block end 308 | for _, v in ipairs(test_aliases) do env[v] = test_block end 309 | 310 | -- Set these functions in the module's meta table to allow accessing 311 | -- telescope's test and context functions without env tricks. This will 312 | -- however add tests to a context table used inside the module, so multiple 313 | -- test files will add tests to the same top-level context, which may or may 314 | -- not be desired. 315 | setmetatable(_M, {__index = env}) 316 | 317 | setmetatable(env, {__index = _G}) 318 | 319 | local func, err = type(target) == 'string' and assert(loadfile(target)) or target 320 | if err then error(err) end 321 | setfenv(func, env)() 322 | return context_table 323 | end 324 | 325 | -- in-place table reverse. 326 | function table.reverse(t) 327 | local len = #t+1 328 | for i=1, (len-1)/2 do 329 | t[i], t[len-i] = t[len-i], t[i] 330 | end 331 | end 332 | 333 | --- Run all tests. 334 | -- This function will exectute each function in the contexts table. 335 | -- @param contexts The contexts created by load_contexts. 336 | -- @param callbacks A table of callback functions to be invoked before or after 337 | -- various test states. 338 | --

339 | -- There is a callback for each test status_code, and callbacks to run 340 | -- before or after each test invocation regardless of outcome. 341 | --

342 | -- 352 | --

353 | -- Callbacks can be used, for example, to drop into a debugger upon a failed 354 | -- assertion or error, for profiling, or updating a GUI progress meter. 355 | --

356 | -- @param test_filter A function to filter tests that match only conditions that you specify. 357 | --

358 | -- For example, the folling would allow you to run only tests whose name matches a pattern: 359 | --

360 | --

361 | -- 362 | -- function(t) return t.name:match("%s* lexer") end 363 | -- 364 | --

365 | -- @return A table of result tables. Each result table has the following 366 | -- fields: 367 | -- 375 | -- @see load_contexts 376 | -- @see status_codes 377 | -- @function run 378 | local function run(contexts, callbacks, test_filter) 379 | 380 | local results = {} 381 | local status_names = invert_table(status_codes) 382 | local test_filter = test_filter or function(a) return a end 383 | 384 | -- Setup a new environment suitable for running a new test 385 | local function newEnv() 386 | local env = {} 387 | 388 | -- Make sure globals are accessible in the new environment 389 | setmetatable(env, {__index = _G}) 390 | 391 | -- Setup all the assert functions in the new environment 392 | for k, v in pairs(assertions) do 393 | setfenv(v, env) 394 | env[k] = v 395 | end 396 | 397 | return env 398 | end 399 | 400 | local env = newEnv() 401 | 402 | local function invoke_callback(name, test) 403 | if not callbacks then return end 404 | if type(callbacks[name]) == "table" then 405 | for _, c in ipairs(callbacks[name]) do c(test) end 406 | elseif callbacks[name] then 407 | callbacks[name](test) 408 | end 409 | end 410 | 411 | local function invoke_test(func) 412 | local assertions_invoked = 0 413 | env.assertion_callback = function() 414 | assertions_invoked = assertions_invoked + 1 415 | end 416 | setfenv(func, env) 417 | local result, message = xpcall(func, debug.traceback) 418 | if result and assertions_invoked > 0 then 419 | return status_codes.pass, assertions_invoked, nil 420 | elseif result then 421 | return status_codes.unassertive, 0, nil 422 | elseif type(message) == "table" then 423 | return status_codes.fail, assertions_invoked, message 424 | else 425 | return status_codes.err, assertions_invoked, {message, debug.traceback()} 426 | end 427 | end 428 | 429 | for i, v in filter(contexts, function(i, v) return v.test and test_filter(v) end) do 430 | env = newEnv() -- Setup a new environment for this test 431 | 432 | local ancestors = ancestors(i, contexts) 433 | local context_name = 'Top level' 434 | if contexts[i].parent ~= 0 then 435 | context_name = contexts[contexts[i].parent].name 436 | end 437 | local result = { 438 | assertions_invoked = 0, 439 | name = contexts[i].name, 440 | context = context_name, 441 | test = i 442 | } 443 | table.sort(ancestors) 444 | -- this "before" is the test callback passed into the runner 445 | invoke_callback("before", result) 446 | 447 | -- run all the "before" blocks/functions 448 | for _, a in ipairs(ancestors) do 449 | if contexts[a].before then 450 | setfenv(contexts[a].before, env) 451 | contexts[a].before() 452 | end 453 | end 454 | 455 | -- check if it's a function because pending tests will just have "true" 456 | if type(v.test) == "function" then 457 | result.status_code, result.assertions_invoked, result.message = invoke_test(v.test) 458 | invoke_callback(status_names[result.status_code], result) 459 | else 460 | result.status_code = status_codes.pending 461 | invoke_callback("pending", result) 462 | end 463 | result.status_label = status_labels[result.status_code] 464 | 465 | -- Run all the "after" blocks/functions 466 | table.reverse(ancestors) 467 | for _, a in ipairs(ancestors) do 468 | if contexts[a].after then 469 | setfenv(contexts[a].after, env) 470 | contexts[a].after() 471 | end 472 | end 473 | 474 | invoke_callback("after", result) 475 | results[i] = result 476 | end 477 | 478 | return results 479 | 480 | end 481 | 482 | --- Return a detailed report for each context, with the status of each test. 483 | -- @param contexts The contexts returned by load_contexts. 484 | -- @param results The results returned by run. 485 | -- @function test_report 486 | local function test_report(contexts, results) 487 | 488 | local buffer = {} 489 | local leading_space = " " 490 | local level = 0 491 | local line_char = "-" 492 | local previous_level = 0 493 | local status_format_len = 3 494 | local status_format = "[%s]" 495 | local width = 72 496 | local context_name_format = "%-" .. width - status_format_len .. "s" 497 | local function_name_format = "%-" .. width - status_format_len .. "s" 498 | 499 | local function space() 500 | return leading_space:rep(level - 1) 501 | end 502 | 503 | local function add_divider() 504 | table.insert(buffer, line_char:rep(width)) 505 | end 506 | add_divider() 507 | for i, item in ipairs(contexts) do 508 | local ancestors = ancestors(i, contexts) 509 | previous_level = level or 0 510 | level = #ancestors 511 | -- the 4 here is the length of "..." plus one space of padding 512 | local name = truncate_string(item.name, width - status_format_len - 4 - #ancestors, '...') 513 | if previous_level ~= level and level == 0 then add_divider() end 514 | if item.context then 515 | table.insert(buffer, context_name_format:format(space() .. name .. ':')) 516 | elseif results[i] then 517 | table.insert(buffer, function_name_format:format(space() .. name) .. 518 | status_format:format(results[i].status_label)) 519 | end 520 | end 521 | add_divider() 522 | return table.concat(buffer, "\n") 523 | 524 | end 525 | 526 | --- Return a table of stack traces for tests which produced a failure or an error. 527 | -- @param contexts The contexts returned by load_contexts. 528 | -- @param results The results returned by run. 529 | -- @function error_report 530 | local function error_report(contexts, results) 531 | local buffer = {} 532 | for _, r in filter(results, function(i, r) return r.message end) do 533 | local name = contexts[r.test].name 534 | table.insert(buffer, name .. ":\n" .. r.message[1] .. "\n" .. r.message[2]) 535 | end 536 | if #buffer > 0 then return table.concat(buffer, "\n") end 537 | end 538 | 539 | --- Get a one-line report and a summary table with the status counts. The 540 | -- counts given are: total tests, assertions, passed tests, failed tests, 541 | -- pending tests, and tests which didn't assert anything. 542 | -- @return A report that can be printed 543 | -- @return A table with the various counts. Its fields are: 544 | -- assertions, errors, failed, passed, 545 | -- pending, tests, unassertive. 546 | -- @param contexts The contexts returned by load_contexts. 547 | -- @param results The results returned by run. 548 | -- @function summary_report 549 | local function summary_report(contexts, results) 550 | local r = { 551 | assertions = 0, 552 | errors = 0, 553 | failed = 0, 554 | passed = 0, 555 | pending = 0, 556 | tests = 0, 557 | unassertive = 0 558 | } 559 | for _, v in pairs(results) do 560 | r.tests = r.tests + 1 561 | r.assertions = r.assertions + v.assertions_invoked 562 | if v.status_code == status_codes.err then r.errors = r.errors + 1 563 | elseif v.status_code == status_codes.fail then r.failed = r.failed + 1 564 | elseif v.status_code == status_codes.pass then r.passed = r.passed + 1 565 | elseif v.status_code == status_codes.pending then r.pending = r.pending + 1 566 | elseif v.status_code == status_codes.unassertive then r.unassertive = r.unassertive + 1 567 | end 568 | end 569 | local buffer = {} 570 | for _, k in ipairs({"tests", "passed", "assertions", "failed", "errors", "unassertive", "pending"}) do 571 | local number = r[k] 572 | local label = k 573 | if number == 1 then 574 | label = label:gsub("s$", "") 575 | end 576 | table.insert(buffer, ("%d %s"):format(number, label)) 577 | end 578 | return table.concat(buffer, " "), r 579 | end 580 | 581 | _M.after_aliases = after_aliases 582 | _M.make_assertion = make_assertion 583 | _M.assertion_message_prefix = assertion_message_prefix 584 | _M.before_aliases = before_aliases 585 | _M.context_aliases = context_aliases 586 | _M.error_report = error_report 587 | _M.load_contexts = load_contexts 588 | _M.run = run 589 | _M.test_report = test_report 590 | _M.status_codes = status_codes 591 | _M.status_labels = status_labels 592 | _M.summary_report = summary_report 593 | _M.test_aliases = test_aliases 594 | _M.version = _VERSION 595 | _M._VERSION = _VERSION 596 | 597 | return _M 598 | -------------------------------------------------------------------------------- /telescope/compat_env.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | compat_env v$(_VERSION) - Lua 5.1/5.2 environment compatibility functions 4 | 5 | SYNOPSIS 6 | 7 | -- Get load/loadfile compatibility functions only if using 5.1. 8 | local CL = pcall(load, '') and _G or require 'compat_env' 9 | local load = CL.load 10 | local loadfile = CL.loadfile 11 | 12 | -- The following now works in both Lua 5.1 and 5.2: 13 | assert(load('return 2*pi', nil, 't', {pi=math.pi}))() 14 | assert(loadfile('ex.lua', 't', {print=print}))() 15 | 16 | -- Get getfenv/setfenv compatibility functions only if using 5.2. 17 | local getfenv = _G.getfenv or require 'compat_env'.getfenv 18 | local setfenv = _G.setfenv or require 'compat_env'.setfenv 19 | local function f() return x end 20 | setfenv(f, {x=2}) 21 | print(x, getfenv(f).x) --> 2, 2 22 | 23 | DESCRIPTION 24 | 25 | This module provides Lua 5.1/5.2 environment related compatibility functions. 26 | This includes implementations of Lua 5.2 style `load` and `loadfile` 27 | for use in Lua 5.1. It also includes Lua 5.1 style `getfenv` and `setfenv` 28 | for use in Lua 5.2. 29 | 30 | API 31 | 32 | local CL = require 'compat_env' 33 | 34 | CL.load (ld [, source [, mode [, env] ] ]) --> f [, err] 35 | 36 | This behaves the same as the Lua 5.2 `load` in both 37 | Lua 5.1 and 5.2. 38 | http://www.lua.org/manual/5.2/manual.html#pdf-load 39 | 40 | CL.loadfile ([filename [, mode [, env] ] ]) --> f [, err] 41 | 42 | This behaves the same as the Lua 5.2 `loadfile` in both 43 | Lua 5.1 and 5.2. 44 | http://www.lua.org/manual/5.2/manual.html#pdf-loadfile 45 | 46 | CL.getfenv ([f]) --> t 47 | 48 | This is identical to the Lua 5.1 `getfenv` in Lua 5.1. 49 | This behaves similar to the Lua 5.1 `getfenv` in Lua 5.2. 50 | When a global environment is to be returned, or when `f` is a 51 | C function, this returns `_G` since Lua 5.2 doesn't have 52 | (thread) global and C function environments. This will also 53 | return `_G` if the Lua function `f` lacks an `_ENV` 54 | upvalue, but it will raise an error if uncertain due to lack of 55 | debug info. It is not normally considered good design to use 56 | this function; when possible, use `load` or `loadfile` instead. 57 | http://www.lua.org/manual/5.1/manual.html#pdf-getfenv 58 | 59 | CL.setfenv (f, t) 60 | 61 | This is identical to the Lua 5.1 `setfenv` in Lua 5.1. 62 | This behaves similar to the Lua 5.1 `setfenv` in Lua 5.2. 63 | This will do nothing if `f` is a Lua function that 64 | lacks an `_ENV` upvalue, but it will raise an error if uncertain 65 | due to lack of debug info. See also Design Notes below. 66 | It is not normally considered good design to use 67 | this function; when possible, use `load` or `loadfile` instead. 68 | http://www.lua.org/manual/5.1/manual.html#pdf-setfenv 69 | 70 | DESIGN NOTES 71 | 72 | This module intends to provide robust and fairly complete reimplementations 73 | of the environment related Lua 5.1 and Lua 5.2 functions. 74 | No effort is made, however, to simulate rare or difficult to simulate features, 75 | such as thread environments, although this is liable to change in the future. 76 | Such 5.1 capabilities are discouraged and ideally 77 | removed from 5.1 code, thereby allowing your code to work in both 5.1 and 5.2. 78 | 79 | In Lua 5.2, a `setfenv(f, {})`, where `f` lacks any upvalues, will be silently 80 | ignored since there is no `_ENV` in this function to write to, and the 81 | environment will have no effect inside the function anyway. However, 82 | this does mean that `getfenv(setfenv(f, t))` does not necessarily equal `t`, 83 | which is incompatible with 5.1 code (a possible workaround would be [1]). 84 | If `setfenv(f, {})` has an upvalue but no debug info, then this will raise 85 | an error to prevent inadvertently executing potentially untrusted code in the 86 | global environment. 87 | 88 | It is not normally considered good design to use `setfenv` and `getfenv` 89 | (one reason they were removed in 5.2). When possible, consider replacing 90 | these with `load` or `loadfile`, which are more restrictive and have native 91 | implementations in 5.2. 92 | 93 | This module might be merged into a more general Lua 5.1/5.2 compatibility 94 | library (e.g. a full reimplementation of Lua 5.2 `_G`). However, 95 | `load/loadfile/getfenv/setfenv` perhaps are among the more cumbersome 96 | functions not to have. 97 | 98 | INSTALLATION 99 | 100 | Download compat_env.lua: 101 | 102 | wget https://raw.github.com/gist/1654007/compat_env.lua 103 | 104 | Copy compat_env.lua into your LUA_PATH. 105 | 106 | Alternately, unpack, test, and install into LuaRocks: 107 | 108 | wget https://raw.github.com/gist/1422205/sourceunpack.lua 109 | lua sourceunpack.lua compat_env.lua 110 | (cd out && luarocks make) 111 | 112 | Related work 113 | 114 | http://lua-users.org/wiki/LuaVersionCompatibility 115 | https://github.com/stevedonovan/Penlight/blob/master/lua/pl/utils.lua 116 | - penlight implementations of getfenv/setfenv 117 | http://lua-users.org/lists/lua-l/2010-06/msg00313.html 118 | - initial getfenv/setfenv implementation 119 | 120 | References 121 | 122 | [1] http://lua-users.org/lists/lua-l/2010-06/msg00315.html 123 | 124 | Copyright 125 | 126 | (c) 2012 David Manura. Licensed under the same terms as Lua 5.1/5.2 (MIT license). 127 | 128 | Permission is hereby granted, free of charge, to any person obtaining a copy 129 | of this software and associated documentation files (the "Software"), to deal 130 | in the Software without restriction, including without limitation the rights 131 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 132 | copies of the Software, and to permit persons to whom the Software is 133 | furnished to do so, subject to the following conditions: 134 | 135 | The above copyright notice and this permission notice shall be included in 136 | all copies or substantial portions of the Software. 137 | 138 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 139 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 140 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 141 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 142 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 143 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 144 | THE SOFTWARE. 145 | 146 | --]]--------------------------------------------------------------------- 147 | 148 | local M = {_TYPE='module', _NAME='compat_env', _VERSION='0.2.20120124'} 149 | 150 | local function check_chunk_type(s, mode) 151 | local nmode = mode or 'bt' 152 | local is_binary = s and #s > 0 and s:byte(1) == 27 153 | if is_binary and not nmode:match'b' then 154 | return nil, ("attempt to load a binary chunk (mode is '%s')"):format(mode) 155 | elseif not is_binary and not nmode:match't' then 156 | return nil, ("attempt to load a text chunk (mode is '%s')"):format(mode) 157 | end 158 | return true 159 | end 160 | 161 | local IS_52_LOAD = pcall(load, '') 162 | if IS_52_LOAD then 163 | M.load = _G.load 164 | M.loadfile = _G.loadfile 165 | else 166 | -- 5.2 style `load` implemented in 5.1 167 | function M.load(ld, source, mode, env) 168 | local f 169 | if type(ld) == 'string' then 170 | local s = ld 171 | local ok, err = check_chunk_type(s, mode); if not ok then return ok, err end 172 | local err; f, err = loadstring(s, source); if not f then return f, err end 173 | elseif type(ld) == 'function' then 174 | local ld2 = ld 175 | if (mode or 'bt') ~= 'bt' then 176 | local first = ld() 177 | local ok, err = check_chunk_type(first, mode); if not ok then return ok, err end 178 | ld2 = function() 179 | if first then 180 | local chunk=first; first=nil; return chunk 181 | else return ld() end 182 | end 183 | end 184 | local err; f, err = load(ld2, source); if not f then return f, err end 185 | else 186 | error(("bad argument #1 to 'load' (function expected, got %s)"):format(type(ld)), 2) 187 | end 188 | if env then setfenv(f, env) end 189 | return f 190 | end 191 | 192 | -- 5.2 style `loadfile` implemented in 5.1 193 | function M.loadfile(filename, mode, env) 194 | if (mode or 'bt') ~= 'bt' then 195 | local ioerr 196 | local fh, err = io.open(filename, 'rb'); if not fh then return fh, err end 197 | local function ld() local chunk; chunk,ioerr = fh:read(4096); return chunk end 198 | local f, err = M.load(ld, filename and '@'..filename, mode, env) 199 | fh:close() 200 | if not f then return f, err end 201 | if ioerr then return nil, ioerr end 202 | return f 203 | else 204 | local f, err = loadfile(filename); if not f then return f, err end 205 | if env then setfenv(f, env) end 206 | return f 207 | end 208 | end 209 | end 210 | 211 | if _G.setfenv then -- Lua 5.1 212 | M.setfenv = _G.setfenv 213 | M.getfenv = _G.getfenv 214 | else -- >= Lua 5.2 215 | -- helper function for `getfenv`/`setfenv` 216 | local function envlookup(f) 217 | local name, val 218 | local up = 0 219 | local unknown 220 | repeat 221 | up=up+1; name, val = debug.getupvalue(f, up) 222 | if name == '' then unknown = true end 223 | until name == '_ENV' or name == nil 224 | if name ~= '_ENV' then 225 | up = nil 226 | if unknown then error("upvalues not readable in Lua 5.2 when debug info missing", 3) end 227 | end 228 | return (name == '_ENV') and up, val, unknown 229 | end 230 | 231 | -- helper function for `getfenv`/`setfenv` 232 | local function envhelper(f, name) 233 | if type(f) == 'number' then 234 | if f < 0 then 235 | error(("bad argument #1 to '%s' (level must be non-negative)"):format(name), 3) 236 | elseif f < 1 then 237 | error("thread environments unsupported in Lua 5.2", 3) --[*] 238 | end 239 | f = debug.getinfo(f+2, 'f').func 240 | elseif type(f) ~= 'function' then 241 | error(("bad argument #1 to '%s' (number expected, got %s)"):format(type(name, f)), 2) 242 | end 243 | return f 244 | end 245 | -- [*] might simulate with table keyed by coroutine.running() 246 | 247 | -- 5.1 style `setfenv` implemented in 5.2 248 | function M.setfenv(f, t) 249 | local f = envhelper(f, 'setfenv') 250 | local up, val, unknown = envlookup(f) 251 | if up then 252 | debug.upvaluejoin(f, up, function() return up end, 1) -- unique upvalue [*] 253 | debug.setupvalue(f, up, t) 254 | else 255 | local what = debug.getinfo(f, 'S').what 256 | if what ~= 'Lua' and what ~= 'main' then -- not Lua func 257 | error("'setfenv' cannot change environment of given object", 2) 258 | end -- else ignore no _ENV upvalue (warning: incompatible with 5.1) 259 | end 260 | -- added in https://gist.github.com/2255007 261 | return f 262 | end 263 | -- [*] http://lua-users.org/lists/lua-l/2010-06/msg00313.html 264 | 265 | -- 5.1 style `getfenv` implemented in 5.2 266 | function M.getfenv(f) 267 | if f == 0 or f == nil then return _G end -- simulated behavior 268 | local f = envhelper(f, 'setfenv') 269 | local up, val = envlookup(f) 270 | if not up then return _G end -- simulated behavior [**] 271 | return val 272 | end 273 | -- [**] possible reasons: no _ENV upvalue, C function 274 | end 275 | 276 | 277 | return M 278 | 279 | --[[ FILE rockspec.in 280 | 281 | package = 'compat_env' 282 | version = '$(_VERSION)-1' 283 | source = { 284 | url = 'https://raw.github.com/gist/1654007/$(GITID)/compat_env.lua', 285 | --url = 'https://raw.github.com/gist/1654007/compat_env.lua', -- latest raw 286 | --url = 'https://gist.github.com/gists/1654007/download', 287 | md5 = '$(MD5)' 288 | } 289 | description = { 290 | summary = 'Lua 5.1/5.2 environment compatibility functions', 291 | detailed = [=[ 292 | Provides Lua 5.1/5.2 environment related compatibility functions. 293 | This includes implementations of Lua 5.2 style `load` and `loadfile` 294 | for use in Lua 5.1. It also includes Lua 5.1 style `getfenv` and `setfenv` 295 | for use in Lua 5.2. 296 | ]=], 297 | license = 'MIT/X11', 298 | homepage = 'https://gist.github.com/1654007', 299 | maintainer = 'David Manura' 300 | } 301 | dependencies = {} -- Lua 5.1 or 5.2 302 | build = { 303 | type = 'builtin', 304 | modules = { 305 | ['compat_env'] = 'compat_env.lua' 306 | } 307 | } 308 | 309 | --]]--------------------------------------------------------------------- 310 | 311 | --[[ FILE test.lua 312 | 313 | -- test.lua - test suite for compat_env module. 314 | 315 | local CL = require 'compat_env' 316 | local load = CL.load 317 | local loadfile = CL.loadfile 318 | local setfenv = CL.setfenv 319 | local getfenv = CL.getfenv 320 | 321 | local function checkeq(a, b, e) 322 | if a ~= b then error( 323 | 'not equal ['..tostring(a)..'] ['..tostring(b)..'] ['..tostring(e)..']') 324 | end 325 | end 326 | local function checkerr(pat, ok, err) 327 | assert(not ok, 'checkerr') 328 | assert(type(err) == 'string' and err:match(pat), err) 329 | end 330 | 331 | -- test `load` 332 | checkeq(load('return 2')(), 2) 333 | checkerr('expected near', load'return 2 2') 334 | checkerr('text chunk', load('return 2', nil, 'b')) 335 | checkerr('text chunk', load('', nil, 'b')) 336 | checkerr('binary chunk', load('\027', nil, 't')) 337 | checkeq(load('return 2*x',nil,'bt',{x=5})(), 10) 338 | checkeq(debug.getinfo(load('')).source, '') 339 | checkeq(debug.getinfo(load('', 'foo')).source, 'foo') 340 | 341 | -- test `loadfile` 342 | local fh = assert(io.open('tmp.lua', 'wb')) 343 | fh:write('return (...) or x') 344 | fh:close() 345 | checkeq(loadfile('tmp.lua')(2), 2) 346 | checkeq(loadfile('tmp.lua', 't')(2), 2) 347 | checkerr('text chunk', loadfile('tmp.lua', 'b')) 348 | checkeq(loadfile('tmp.lua', nil, {x=3})(), 3) 349 | checkeq(debug.getinfo(loadfile('tmp.lua')).source, '@tmp.lua') 350 | checkeq(debug.getinfo(loadfile('tmp.lua', 't', {})).source, '@tmp.lua') 351 | os.remove'tmp.lua' 352 | 353 | -- test `setfenv`/`getfenv` 354 | x = 5 355 | local a,b=true; local function f(c) if a then return x,b,c end end 356 | setfenv(f, {x=3}) 357 | checkeq(f(), 3) 358 | checkeq(getfenv(f).x, 3) 359 | checkerr('cannot change', pcall(setfenv, string.len, {})) -- C function 360 | checkeq(getfenv(string.len), _G) -- C function 361 | local function g() 362 | setfenv(1, {x=4}) 363 | checkeq(getfenv(1).x, 4) 364 | return x 365 | end 366 | checkeq(g(), 4) -- numeric level 367 | if _G._VERSION ~= 'Lua 5.1' then 368 | checkerr('unsupported', pcall(setfenv, 0, {})) 369 | end 370 | checkeq(getfenv(0), _G) 371 | checkeq(getfenv(), _G) -- no arg 372 | checkeq(x, 5) -- main unaltered 373 | setfenv(function()end, {}) -- no upvalues, ignore 374 | checkeq(getfenv(function()end), _G) -- no upvaluse 375 | if _G._VERSION ~= 'Lua 5.1' then 376 | checkeq(getfenv(setfenv(function()end, {})), _G) -- warning: incompatible with 5.1 377 | end 378 | x = nil 379 | 380 | print 'OK' 381 | 382 | --]]--------------------------------------------------------------------- 383 | 384 | --[[ FILE CHANGES.txt 385 | 0.2.20120124 386 | Renamed module to compat_env (from compat_load) 387 | Add getfenv/setfenv functions 388 | 389 | 0.1.20120121 390 | Initial public release 391 | --]] 392 | -------------------------------------------------------------------------------- /tsc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env lua 2 | local telescope = require 'telescope' 3 | 4 | pcall(require, "luarocks.require") 5 | pcall(require, "shake") 6 | 7 | package.path = "./?.lua;" .. package.path 8 | 9 | local function luacov_report() 10 | local luacov = require("luacov.stats") 11 | local data = luacov.load_stats() 12 | if not data then 13 | print("Could not load stats file "..luacov.statsfile..".") 14 | print("Run your Lua program with -lluacov and then rerun luacov.") 15 | os.exit(1) 16 | end 17 | local report = io.open("coverage.html", "w") 18 | report:write('', "\n") 19 | report:write([[ 20 | 21 | 22 | 23 | Luacov Coverage Report 24 | 25 | 40 | 41 | 42 |
43 |

Luacov Code Coverage Report

44 | ]]) 45 | report:write("

Generated on ", os.date(), "

\n") 46 | 47 | local names = {} 48 | for filename, _ in pairs(data) do 49 | table.insert(names, filename) 50 | end 51 | 52 | local escapes = { 53 | [">"] = ">", 54 | ["<"] = "<" 55 | } 56 | local function escape_html(str) 57 | return str:gsub("[<>]", function(a) return escapes[a] end) 58 | end 59 | 60 | table.sort(names) 61 | 62 | for _, filename in ipairs(names) do 63 | if string.match(filename, "/luacov/") or 64 | string.match(filename, "/luarocks/") or 65 | string.match(filename, "/tsc$") 66 | then 67 | break 68 | end 69 | local filedata = data[filename] 70 | filename = string.gsub(filename, "^%./", "") 71 | local file = io.open(filename, "r") 72 | if file then 73 | report:write("

", filename, "

", "\n") 74 | report:write("
") 75 | report:write("
    ", "\n") 76 | local line_nr = 1 77 | while true do 78 | local line = file:read("*l") 79 | if not line then break end 80 | if line:match("^%s*%-%-") then -- Comment line 81 | 82 | elseif line:match("^%s*$") -- Empty line 83 | or line:match("^%s*end,?%s*$") -- Single "end" 84 | or line:match("^%s*else%s*$") -- Single "else" 85 | or line:match("^%s*{%s*$") -- Single opening brace 86 | or line:match("^%s*}%s*$") -- Single closing brace 87 | or line:match("^#!") -- Unix hash-bang magic line 88 | then 89 | report:write("
  • ", string.format("%-4d", line_nr), "      ", escape_html(line), "
  • ", "\n") 90 | else 91 | local hits = filedata[line_nr] 92 | local class = "uncovered" 93 | if not hits then hits = 0 end 94 | if hits > 0 then class = "covered" end 95 | report:write("
  • ", "
    ", string.format("%-4d", line_nr), string.format("%-4d", hits), " ", escape_html(line), "
  • ", "\n") 96 | end 97 | line_nr = line_nr + 1 98 | end 99 | end 100 | report:write("
", "\n") 101 | report:write("
", "\n") 102 | end 103 | report:write([[ 104 |
105 | 106 | 107 | ]]) 108 | end 109 | 110 | local function getopt(arg, options) 111 | local tab = {} 112 | for k, v in ipairs(arg) do 113 | if string.sub(v, 1, 2) == "--" then 114 | local x = string.find(v, "=", 1, true) 115 | if x then tab[string.sub(v, 3, x - 1)] = string.sub(v, x + 1) 116 | else tab[string.sub(v, 3)] = true 117 | end 118 | elseif string.sub(v, 1, 1) == "-" then 119 | local y = 2 120 | local l = string.len(v) 121 | local jopt 122 | while (y <= l) do 123 | jopt = string.sub(v, y, y) 124 | if string.find(options, jopt, 1, true) then 125 | if y < l then 126 | tab[jopt] = string.sub(v, y + 1) 127 | y = l 128 | else 129 | tab[jopt] = arg[k + 1] 130 | end 131 | else 132 | tab[jopt] = true 133 | end 134 | y = y + 1 135 | end 136 | end 137 | end 138 | return tab 139 | end 140 | 141 | local callbacks = {} 142 | 143 | local function progress_meter(t) 144 | io.stdout:write(t.status_label) 145 | end 146 | 147 | local function show_usage() 148 | local text = [[ 149 | Telescope 150 | 151 | Usage: tsc [options] [files] 152 | 153 | Description: 154 | Telescope is a test framework for Lua that allows you to write tests 155 | and specs in a TDD or BDD style. 156 | 157 | Options: 158 | 159 | -f, --full Show full report 160 | -q, --quiet Show don't show any stack traces 161 | -s --silent Don't show any output 162 | -h,-? --help Show this text 163 | -v --version Show version 164 | -c --luacov Output a coverage file using Luacov (http://luacov.luaforge.net/) 165 | --load= Load a Lua file before executing command 166 | --name= Only run tests whose name matches a Lua string pattern 167 | --shake Use shake as the front-end for tests 168 | 169 | Callback options: 170 | --after= Run function given after each test 171 | --before= Run function before each test 172 | --err= Run function after each test that produces an error 173 | --fail Run function after each failing test 174 | --pass= Run function after each passing test 175 | --pending= Run function after each pending test 176 | --unassertive= Run function after each unassertive test 177 | 178 | An example callback: 179 | 180 | tsc --after="function(t) print(t.status_label, t.name, t.context) end" example.lua 181 | 182 | An example test: 183 | 184 | context("A context", function() 185 | before(function() end) 186 | after(function() end) 187 | context("A nested context", function() 188 | test("A test", function() 189 | assert_not_equal("ham", "cheese") 190 | end) 191 | context("Another nested context", function() 192 | test("Another test", function() 193 | assert_greater_than(2, 1) 194 | end) 195 | end) 196 | end) 197 | test("A test in the top-level context", function() 198 | assert_equal(1, 1) 199 | end) 200 | end) 201 | 202 | Project home: 203 | http://telescope.luaforge.net/ 204 | 205 | License: 206 | MIT/X11 (Same as Lua) 207 | 208 | Author: 209 | Norman Clarke . Please feel free to email bug 210 | reports, feedback and feature requests. 211 | ]] 212 | print(text) 213 | end 214 | 215 | local function add_callback(callback, func) 216 | if callbacks[callback] then 217 | if type(callbacks[callback]) ~= "table" then 218 | callbacks[callback] = {callbacks[callback]} 219 | end 220 | table.insert(callbacks[callback], func) 221 | else 222 | callbacks[callback] = func 223 | end 224 | end 225 | 226 | local function process_args() 227 | local files = {} 228 | local opts = getopt(arg, "") 229 | local i = 1 230 | for _, _ in pairs(opts) do i = i+1 end 231 | while i <= #arg do table.insert(files, arg[i]) ; i = i + 1 end 232 | return opts, files 233 | end 234 | local opts, files = process_args() 235 | if opts["h"] or opts["?"] or opts["help"] or not (next(opts) or next(files)) then 236 | show_usage() 237 | os.exit() 238 | end 239 | 240 | if opts.v or opts.version then 241 | print(telescope.version) 242 | os.exit(0) 243 | end 244 | 245 | if opts.c or opts.luacov then 246 | require "luacov.tick" 247 | end 248 | 249 | -- load a file with custom functionality if desired 250 | if opts["load"] then dofile(opts["load"]) end 251 | 252 | local test_pattern 253 | if opts["name"] then 254 | test_pattern = function(t) return t.name:match(opts["name"]) end 255 | end 256 | 257 | -- set callbacks passed on command line 258 | local callback_args = { "after", "before", "err", "fail", "pass", 259 | "pending", "unassertive" } 260 | for _, callback in ipairs(callback_args) do 261 | if opts[callback] then 262 | add_callback(callback, loadstring(opts[callback])()) 263 | end 264 | end 265 | 266 | local contexts = {} 267 | if opts["shake"] then 268 | for _, file in ipairs(files) do shake.load_contexts(file, contexts) end 269 | else 270 | for _, file in ipairs(files) do telescope.load_contexts(file, contexts) end 271 | end 272 | 273 | local buffer = {} 274 | local results = telescope.run(contexts, callbacks, test_pattern) 275 | local summary, data = telescope.summary_report(contexts, results) 276 | 277 | if opts.f or opts.full then 278 | table.insert(buffer, telescope.test_report(contexts, results)) 279 | end 280 | 281 | if not opts.s and not opts.silent then 282 | table.insert(buffer, summary) 283 | if not opts.q and not opts.quiet then 284 | local report = telescope.error_report(contexts, results) 285 | if report then 286 | table.insert(buffer, "") 287 | table.insert(buffer, report) 288 | end 289 | end 290 | end 291 | 292 | if #buffer > 0 then print(table.concat(buffer, "\n")) end 293 | 294 | if opts.c or opts.coverage then 295 | luacov_report() 296 | os.remove("luacov.stats.out") 297 | end 298 | 299 | for _, v in pairs(results) do 300 | if v.status_code == telescope.status_codes.err or 301 | v.status_code == telescope.status_codes.fail then 302 | os.exit(1) 303 | end 304 | end 305 | --------------------------------------------------------------------------------