├── LICENSE ├── idn.lua ├── telescope.lua ├── tests.lua └── tsc /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Trond A Ekseth 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /idn.lua: -------------------------------------------------------------------------------- 1 | local bit = require'bit' 2 | 3 | local base = 36 4 | local tmin = 1 5 | local tmax = 26 6 | local skew = 38 7 | local damp = 700 8 | local initial_bias = 72 9 | local initial_n = 0x80 10 | local delimiter = 0x2D 11 | 12 | -- Bias adaptation function 13 | local adapt = function(delta, numpoints, firsttime) 14 | if(firsttime) then 15 | delta = math.floor(delta / damp) 16 | else 17 | delta = bit.rshift(delta, 1) 18 | end 19 | 20 | delta = delta + math.floor(delta / numpoints) 21 | 22 | local k = 0 23 | while(delta > math.floor(((base - tmin) * tmax) / 2)) do 24 | delta = math.floor(delta / (base - tmin)) 25 | k = k + base 26 | end 27 | 28 | return math.floor(k + (base - tmin + 1) * delta / (delta + skew)) 29 | end 30 | 31 | -- tests whether cp is a basic code point: 32 | local basic = function(cp) 33 | return cp < 0x80 34 | end 35 | 36 | local offset = {0, 0x3000, 0xE0000, 0x3C00000} 37 | local utf8code = function(U, ...) 38 | local numBytes = select('#', ...) 39 | for i=1, numBytes do 40 | local b = select(i, ...) 41 | U = bit.lshift(U, 6) + bit.band(b, 63) 42 | end 43 | 44 | return U - offset[numBytes + 1] 45 | end 46 | 47 | local toUCS4 = function(str) 48 | local out = {} 49 | for c in str:gmatch'([%z\1-\127\194-\244][\128-\191]*)' do 50 | table.insert(out, utf8code(string.byte(c, 1, -1))) 51 | end 52 | 53 | return out 54 | end 55 | 56 | local toUnicode = function(n) 57 | if(n < 128) then 58 | return string.char(n) 59 | elseif(n < 2048) then 60 | return string.char(192 + ((n - (n % 64)) / 64), 128 + (n % 64)) 61 | else 62 | return string.char(224 + ((n - (n % 4096)) / 4096), 128 + (((n % 4096) - (n % 64)) / 64), 128 + (n % 64)) 63 | end 64 | end 65 | 66 | local punycode_encode 67 | do 68 | -- returns the basic code point whose value 69 | -- (when used for representing integers) is d, which needs to be in 70 | -- the range 0 to base-1. 71 | local encode_digit = function(d) 72 | return d + 22 + 75 * (d < 26 and 1 or 0) 73 | -- 0..25 map to ASCII a..z 74 | -- 26..35 map to ASCII 0..9 75 | end 76 | 77 | function punycode_encode(input) 78 | if(type(input) == 'string') then 79 | input = toUCS4(input) 80 | end 81 | 82 | local output = {} 83 | 84 | -- Initialize the state 85 | local n = initial_n 86 | local delta = 0 87 | local bias = initial_bias 88 | 89 | -- Handle the basic code poinst 90 | for j = 1, #input do 91 | local c = input[j] 92 | if(basic(c)) then 93 | table.insert(output, string.char(c)) 94 | end 95 | end 96 | 97 | local h = #output 98 | local b = h 99 | 100 | -- h is the number of code points that have been handled, b is the 101 | -- number of basic code points. 102 | 103 | if(b > 0) then 104 | table.insert(output, string.char(delimiter)) 105 | end 106 | 107 | -- Main encoding loop 108 | while(h < #input) do 109 | -- All non-basic code points < n have been 110 | -- handled already. Find the next larger one 111 | local m = math.huge 112 | for j = 1, #input do 113 | local c = input[j] 114 | if(c >= n and c < m) then 115 | m = c 116 | end 117 | end 118 | 119 | delta = delta + (m - n) * (h + 1) 120 | n = m 121 | 122 | for j = 1, #input do 123 | local cp = input[j] 124 | if(cp < n) then 125 | delta = delta + 1 126 | end 127 | 128 | if(cp == n) then 129 | local q = delta 130 | local k = base 131 | while(true) do 132 | local t 133 | if(k <= bias) then 134 | t = tmin 135 | else 136 | if(k >= bias + tmax) then 137 | t = tmax 138 | else 139 | t = k - bias 140 | end 141 | end 142 | 143 | if(q < t) then break end 144 | 145 | table.insert(output, string.char(encode_digit(t + (q - t) % (base - t)))) 146 | q = math.floor((q - t) / (base - t)) 147 | 148 | k = k + base 149 | end 150 | 151 | table.insert(output, string.char(encode_digit(q))) 152 | bias = adapt(delta, h + 1, h == b) 153 | delta = 0 154 | h = h +1 155 | end 156 | end 157 | 158 | delta = delta + 1 159 | n = n + 1 160 | end 161 | 162 | return table.concat(output) 163 | end 164 | end 165 | 166 | local punycode_decode 167 | do 168 | local decode_digit = function(d) 169 | if(d - 48 < 10) then 170 | return d - 22 171 | elseif(d - 65 < 26) then 172 | return d - 65 173 | elseif(d - 97 < 26) then 174 | return d - 97 175 | else 176 | return base 177 | end 178 | end 179 | 180 | function punycode_decode(input) 181 | if(type(input) == 'string') then 182 | input = toUCS4(input) 183 | end 184 | 185 | local output = {} 186 | 187 | -- Initialize the state 188 | local n = initial_n 189 | local i = 0 190 | local out = 1 191 | local bias = initial_bias 192 | 193 | local b = 1 194 | for j = 1, #input do 195 | if(input[j] == delimiter) then 196 | b = j 197 | end 198 | end 199 | 200 | for j = 1, #input do 201 | local c = input[j] 202 | if(not basic(c)) then return nil, 'Invalid input' end 203 | end 204 | 205 | for j = 1, b - 1 do 206 | local c = input[j] 207 | output[out] = toUnicode(c) 208 | out = out + 1 209 | end 210 | 211 | local index = 1 212 | if(b > 1) then 213 | index = b + 1 214 | end 215 | 216 | local inputLength = #input 217 | while(index <= inputLength) do 218 | local oldi = i 219 | local w = 1 220 | local k = base 221 | 222 | while(true) do 223 | if(index > inputLength) then return nil, 'Bad input' end 224 | local digit = decode_digit(input[index]) 225 | if(digit >= base) then return nil, 'Bad input' end 226 | index = index + 1 227 | i = i + (digit * w) 228 | 229 | local t 230 | if(k <= bias) then 231 | t = tmin 232 | elseif(k >= bias + tmax) then 233 | t = tmax 234 | else 235 | t = k - bias 236 | end 237 | 238 | if(digit < t) then 239 | break 240 | end 241 | 242 | w = w * (base - t) 243 | 244 | k = k + base 245 | end 246 | 247 | bias = adapt(i - oldi, out, oldi == 0) 248 | 249 | n = n + math.floor(i / (out)) 250 | i = (i % out) + 1 251 | 252 | table.insert(output, i, toUnicode(n)) 253 | out = out + 1 254 | end 255 | 256 | return table.concat(output) 257 | end 258 | end 259 | 260 | local idn_encode 261 | do 262 | function idn_encode(domain) 263 | local labels = {} 264 | for label in domain:gmatch('([^.]+)%.?') do 265 | -- Domain names can only consist of a-z, A-Z, 0-9, - and aren't allowed 266 | -- to start or end with a hyphen 267 | local first, last = label:sub(1, 1), label:sub(-1) 268 | if(first == '-' or last == '-') then 269 | return nil, 'Invalid DNS label' 270 | end 271 | 272 | if(label:match('^[a-zA-Z0-9-]+$')) then 273 | table.insert(labels, label) 274 | elseif(label:sub(1,1) ~= '-' and label:sub(2,2) ~= '-') then 275 | local plabel = punycode_encode(label) 276 | table.insert(labels, string.format('xn--%s', plabel)) 277 | end 278 | end 279 | 280 | return table.concat(labels, '.') 281 | end 282 | end 283 | 284 | local idn_decode 285 | do 286 | function idn_decode(domain) 287 | local labels = {} 288 | for label in domain:gmatch('([^.]+)%.?') do 289 | if(label:sub(1, 4) == 'xn--') then 290 | table.insert(labels, punycode_decode(label:sub(5))) 291 | elseif(label:match('^[a-zA-Z0-9-]+$')) then 292 | table.insert(labels, label) 293 | end 294 | end 295 | 296 | return table.concat(labels, '.') 297 | end 298 | end 299 | 300 | return { 301 | encode = idn_encode, 302 | decode = idn_decode, 303 | 304 | punycode = { 305 | encode = punycode_encode, 306 | decode = punycode_decode, 307 | }, 308 | } 309 | -------------------------------------------------------------------------------- /telescope.lua: -------------------------------------------------------------------------------- 1 | local _G = _G 2 | local assert = assert 3 | local getfenv = getfenv 4 | local io = io 5 | local ipairs = ipairs 6 | local loadfile = loadfile 7 | local next = next 8 | local pairs = pairs 9 | local require = require 10 | local setfenv = setfenv 11 | local setmetatable = setmetatable 12 | local table = table 13 | local type = type 14 | 15 | --- Telescope is a test library for Lua that allows for flexible, declarative 16 | -- tests. The documentation produced here is intended largely for developers 17 | -- working on Telescope. For information on using Telescope, please visit the 18 | -- project homepage at: http://github.com/norman/telescope. 19 | -- @release 0.4 20 | module 'telescope' 21 | 22 | --- The status codes that can be returned by an invoked test. These should not be overidden. 23 | -- @name status_codes 24 | -- @class table 25 | -- @field err - This is returned when an invoked test results in an error 26 | -- rather than a passed or failed assertion. 27 | -- @field fail - This is returned when an invoked test contains one or more failing assertions. 28 | -- @field pass - This is returned when all of a test's assertions pass. 29 | -- @field pending - This is returned when a test does not have a corresponding function. 30 | -- @field unassertive - This is returned when an invoked test does not produce 31 | -- errors, but does not contain any assertions. 32 | status_codes = { 33 | err = 2, 34 | fail = 4, 35 | pass = 8, 36 | pending = 16, 37 | unassertive = 32 38 | } 39 | 40 | --- Labels used to show the various status_codes as a single character. 41 | -- These can be overidden if you wish. 42 | -- @name status_labels 43 | -- @class table 44 | -- @see status_codes 45 | -- @field status_codes.err 'E' 46 | -- @field status_codes.fail 'F' 47 | -- @field status_codes.pass 'P' 48 | -- @field status_codes.pending '?' 49 | -- @field status_codes.unassertive 'U' 50 | 51 | status_labels = { 52 | [status_codes.err] = 'E', 53 | [status_codes.fail] = 'F', 54 | [status_codes.pass] = 'P', 55 | [status_codes.pending] = '?', 56 | [status_codes.unassertive] = 'U' 57 | } 58 | 59 | --- The default names for context blocks. It defaults to "context", "spec" and 60 | -- "describe." 61 | -- @name context_aliases 62 | -- @class table 63 | context_aliases = {"context", "describe", "spec"} 64 | --- The default names for test blocks. It defaults to "test," "it", "expect", 65 | -- "they" and "should." 66 | -- @name test_aliases 67 | -- @class table 68 | test_aliases = {"test", "it", "expect", "should", "they"} 69 | 70 | --- The default names for "before" blocks. It defaults to "before" and "setup." 71 | -- The function in the before block will be run before each sibling test function 72 | -- or context. 73 | -- @name before_aliases 74 | -- @class table 75 | before_aliases = {"before", "setup"} 76 | 77 | --- The default names for "after" blocks. It defaults to "after" and "teardown." 78 | -- The function in the after block will be run after each sibling test function 79 | -- or context. 80 | -- @name after_aliases 81 | -- @class table 82 | after_aliases = {"after", "teardown"} 83 | 84 | -- Prefix to place before all assertion messages. Used by make_assertion(). 85 | assertion_message_prefix = "Assert failed: expected " 86 | 87 | --- The default assertions. 88 | -- These are the assertions built into telescope. You can override them or 89 | -- create your own custom assertions using make_assertion. 90 | -- 118 | -- @see make_assertion 119 | -- @name assertions 120 | -- @class table 121 | assertions = {} 122 | 123 | --- Create a custom assertion. 124 | -- This creates an assertion along with a corresponding negative assertion. It 125 | -- is used internally by telescope to create the default assertions. 126 | -- @param name The base name of the assertion. 127 | --

128 | -- The name will be used as the basis of the positive and negative assertions; 129 | -- i.e., the name equal would be used to create the assertions 130 | -- assert_equal and assert_not_equal. 131 | --

132 | -- @param message The base message that will be shown. 133 | --

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

142 | -- @param func The assertion function itself. 143 | --

144 | -- The assertion function can have any number of arguments. 145 | --

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

257 | -- The resulting contexts table's structure is as follows: 258 | --

259 | -- 260 | -- { 261 | -- {parent = 0, name = "this is a context", context = true}, 262 | -- {parent = 1, name = "this is a nested context", context = true}, 263 | -- {parent = 2, name = "this is a test", test = function}, 264 | -- {parent = 2, name = "this is another test", test = function}, 265 | -- {parent = 0, name = "this is test outside any context", test = function}, 266 | -- } 267 | -- 268 | -- @param contexts A optional table in which to collect the resulting contexts 269 | -- and function. 270 | function load_contexts(path, contexts) 271 | 272 | local env = getfenv() 273 | local current_index = 0 274 | local context_table = contexts or {} 275 | 276 | local function context_block(name, func) 277 | table.insert(context_table, {parent = current_index, name = name, context = true}) 278 | local previous_index = current_index 279 | current_index = #context_table 280 | func() 281 | current_index = previous_index 282 | end 283 | 284 | local function test_block(name, func) 285 | local test_table = {name = name, parent = current_index, test = func or true} 286 | if current_index ~= 0 then 287 | test_table.context_name = context_table[current_index].name 288 | else 289 | test_table.context_name = 'top level' 290 | end 291 | table.insert(context_table, test_table) 292 | end 293 | 294 | local function before_block(func) 295 | context_table[current_index].before = func 296 | end 297 | 298 | local function after_block(func) 299 | context_table[current_index].after = func 300 | end 301 | 302 | for _, v in ipairs(after_aliases) do env[v] = after_block end 303 | for _, v in ipairs(before_aliases) do env[v] = before_block end 304 | for _, v in ipairs(context_aliases) do env[v] = context_block end 305 | for _, v in ipairs(test_aliases) do env[v] = test_block end 306 | 307 | setmetatable(env, {__index = _G}) 308 | local func, err = assert(loadfile(path)) 309 | if err then error(err) end 310 | setfenv(func, env)() 311 | return context_table 312 | end 313 | 314 | --- Run all tests. 315 | -- This function will exectute each function in the contexts table. 316 | -- @param contexts The contexts created by load_contexts. 317 | -- @param callbacks A table of callback functions to be invoked before or after 318 | -- various test states. 319 | --

320 | -- There is a callback for each test status_code, and callbacks to run 321 | -- before or after each test invocation regardless of outcome. 322 | --

323 | -- 333 | --

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

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

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

341 | --

342 | -- 343 | -- function(t) return t.name:match("%s* lexer") end 344 | -- 345 | --

346 | -- @return A table of result tables. Each result table has the following 347 | -- fields: 348 | -- 356 | -- @see load_contexts 357 | -- @see status_codes 358 | function run(contexts, callbacks, test_filter) 359 | 360 | local results = {} 361 | local env = getfenv() 362 | local status_names = invert_table(status_codes) 363 | local test_filter = test_filter or function(a) return a end 364 | 365 | for k, v in pairs(assertions) do 366 | setfenv(v, env) 367 | env[k] = v 368 | end 369 | 370 | local function invoke_callback(name, test) 371 | if not callbacks then return end 372 | if type(callbacks[name]) == "table" then 373 | for _, c in ipairs(callbacks[name]) do c(test) end 374 | elseif callbacks[name] then 375 | callbacks[name](test) 376 | end 377 | end 378 | 379 | local function invoke_test(func) 380 | local assertions_invoked = 0 381 | env.assertion_callback = function() 382 | assertions_invoked = assertions_invoked + 1 383 | end 384 | setfenv(func, env) 385 | local result, message = pcall(func) 386 | if result and assertions_invoked > 0 then 387 | return status_codes.pass, assertions_invoked, nil 388 | elseif result then 389 | return status_codes.unassertive, 0, nil 390 | elseif type(message) == "table" then 391 | return status_codes.fail, assertions_invoked, message 392 | else 393 | return status_codes.err, assertions_invoked, {message, debug.traceback()} 394 | end 395 | end 396 | 397 | for i, v in filter(contexts, function(i, v) return v.test and test_filter(v) end) do 398 | local ancestors = ancestors(i, contexts) 399 | local context_name = 'Top level' 400 | if contexts[i].parent ~= 0 then 401 | context_name = contexts[contexts[i].parent].name 402 | end 403 | local result = { 404 | assertions_invoked = 0, 405 | name = contexts[i].name, 406 | context = context_name, 407 | test = i 408 | } 409 | table.sort(ancestors) 410 | -- this "before" is the test callback passed into the runner 411 | invoke_callback("before", result) 412 | -- this "before" is the "before" block in the test. 413 | for _, a in ipairs(ancestors) do 414 | if contexts[a].before then contexts[a].before() end 415 | end 416 | -- check if it's a function because pending tests will just have "true" 417 | if type(v.test) == "function" then 418 | result.status_code, result.assertions_invoked, result.message = invoke_test(v.test) 419 | invoke_callback(status_names[result.status_code], result) 420 | else 421 | result.status_code = status_codes.pending 422 | invoke_callback("pending", result) 423 | end 424 | result.status_label = status_labels[result.status_code] 425 | for _, a in ipairs(ancestors) do 426 | if contexts[a].after then contexts[a].after() end 427 | end 428 | invoke_callback("after", result) 429 | results[i] = result 430 | end 431 | 432 | return results 433 | 434 | end 435 | 436 | --- Return a detailed report for each context, with the status of each test. 437 | -- @param contexts The contexts returned by load_contexts. 438 | -- @param results The results returned by run. 439 | function test_report(contexts, results) 440 | 441 | local buffer = {} 442 | local leading_space = " " 443 | local level = 0 444 | local line_char = "-" 445 | local previous_level = 0 446 | local status_format_len = 3 447 | local status_format = "[%s]" 448 | local width = 72 449 | local context_name_format = "%-" .. width - status_format_len .. "s" 450 | local function_name_format = "%-" .. width - status_format_len .. "s" 451 | 452 | local function space() 453 | return leading_space:rep(level - 1) 454 | end 455 | 456 | local function add_divider() 457 | table.insert(buffer, line_char:rep(width)) 458 | end 459 | add_divider() 460 | for i, item in ipairs(contexts) do 461 | local ancestors = ancestors(i, contexts) 462 | previous_level = level or 0 463 | level = #ancestors 464 | -- the 4 here is the length of "..." plus one space of padding 465 | local name = truncate_string(item.name, width - status_format_len - 4 - #ancestors, '...') 466 | if previous_level ~= level and level == 0 then add_divider() end 467 | if item.context then 468 | table.insert(buffer, context_name_format:format(space() .. name .. ':')) 469 | elseif results[i] then 470 | table.insert(buffer, function_name_format:format(space() .. name) .. 471 | status_format:format(results[i].status_label)) 472 | end 473 | end 474 | add_divider() 475 | return table.concat(buffer, "\n") 476 | 477 | end 478 | 479 | --- Return a table of stack traces for tests which produced a failure or an error. 480 | -- @param contexts The contexts returned by load_contexts. 481 | -- @param results The results returned by run. 482 | function error_report(contexts, results) 483 | local buffer = {} 484 | for _, r in filter(results, function(i, r) return r.message end) do 485 | local name = contexts[r.test].name 486 | table.insert(buffer, name .. ":\n" .. r.message[1] .. "\n" .. r.message[2]) 487 | end 488 | if #buffer > 0 then return table.concat(buffer, "\n") end 489 | end 490 | 491 | --- Get a one-line report and a summary table with the status counts. The 492 | -- counts given are: total tests, assertions, passed tests, failed tests, 493 | -- pending tests, and tests which didn't assert anything. 494 | -- @return A report that can be printed 495 | -- @return A table with the various counts. Its fields are: 496 | -- assertions, errors, failed, passed, 497 | -- pending, tests, unassertive. 498 | -- @param contexts The contexts returned by load_contexts. 499 | -- @param results The results returned by run. 500 | function summary_report(contexts, results) 501 | local r = { 502 | assertions = 0, 503 | errors = 0, 504 | failed = 0, 505 | passed = 0, 506 | pending = 0, 507 | tests = 0, 508 | unassertive = 0 509 | } 510 | for _, v in pairs(results) do 511 | r.tests = r.tests + 1 512 | r.assertions = r.assertions + v.assertions_invoked 513 | if v.status_code == status_codes.err then r.errors = r.errors + 1 514 | elseif v.status_code == status_codes.fail then r.failed = r.failed + 1 515 | elseif v.status_code == status_codes.pass then r.passed = r.passed + 1 516 | elseif v.status_code == status_codes.pending then r.pending = r.pending + 1 517 | elseif v.status_code == status_codes.unassertive then r.unassertive = r.unassertive + 1 518 | end 519 | end 520 | local buffer = {} 521 | for _, k in ipairs({"tests", "passed", "assertions", "failed", "errors", "unassertive", "pending"}) do 522 | local number = r[k] 523 | local label = k 524 | if number == 1 then 525 | label = label:gsub("s$", "") 526 | end 527 | table.insert(buffer, ("%d %s"):format(number, label)) 528 | end 529 | return table.concat(buffer, " "), r 530 | end 531 | -------------------------------------------------------------------------------- /tests.lua: -------------------------------------------------------------------------------- 1 | local idn = require'idn' 2 | 3 | local prepare = function(str) 4 | return (str:gsub('[\n%s]+', ''):gsub('[uU]%+([A-F0-9]+)%s?', function(n) 5 | n = tonumber(n, 16) 6 | if(n < 128) then 7 | return string.char(n) 8 | elseif(n < 2048) then 9 | return string.char(192 + ((n - (n % 64)) / 64), 128 + (n % 64)) 10 | else 11 | return string.char(224 + ((n - (n % 4096)) / 4096), 128 + (((n % 4096) - (n % 64)) / 64), 128 + (n % 64)) 12 | end 13 | end)) 14 | end 15 | 16 | local pe = idn.punycode.encode 17 | local pd = idn.punycode.decode 18 | local ie = idn.encode 19 | 20 | context("RFC 3492 - Sample Strings", function() 21 | context("Encoding", function() 22 | test("Arabic (Egyptian)", function() 23 | assert_equal(pe(prepare[[ 24 | u+0644 u+064A u+0647 u+0645 u+0627 u+0628 u+062A u+0643 u+0644 25 | u+0645 u+0648 u+0634 u+0639 u+0631 u+0628 u+064A u+061F 26 | ]]), 'egbpdaj6bu4bxfgehfvwxn') 27 | end) 28 | 29 | test("Chinese (simplified)", function() 30 | assert_equal(pe(prepare[[ 31 | u+4ED6 u+4EEC u+4E3A u+4EC0 u+4E48 u+4E0D u+8BF4 u+4E2D u+6587 32 | ]]), 'ihqwcrb4cv8a8dqg056pqjye') 33 | end) 34 | 35 | test("Chinese (traditional)", function() 36 | assert_equal(pe(prepare[[ 37 | u+4ED6 u+5011 u+7232 u+4EC0 u+9EBD u+4E0D u+8AAA u+4E2D u+6587 38 | ]]), 'ihqwctvzc91f659drss3x8bo0yb') 39 | end) 40 | 41 | test("Czech", function() 42 | assert_equal(pe(prepare[[ 43 | U+0050 u+0072 u+006F u+010D u+0070 u+0072 u+006F u+0073 u+0074 44 | u+011B u+006E u+0065 u+006D u+006C u+0075 u+0076 u+00ED u+010D 45 | u+0065 u+0073 u+006B u+0079 46 | ]]), 'Proprostnemluvesky-uyb24dma41a') 47 | end) 48 | 49 | test("Hebrew", function() 50 | assert_equal(pe(prepare[[ 51 | u+05DC u+05DE u+05D4 u+05D4 u+05DD u+05E4 u+05E9 u+05D5 u+05D8 52 | u+05DC u+05D0 u+05DE u+05D3 u+05D1 u+05E8 u+05D9 u+05DD u+05E2 53 | u+05D1 u+05E8 u+05D9 u+05EA 54 | ]]), '4dbcagdahymbxekheh6e0a7fei0b') 55 | end) 56 | 57 | test("Hindi (Devanagari)", function() 58 | assert_equal(pe(prepare[[ 59 | u+092F u+0939 u+0932 u+094B u+0917 u+0939 u+093F u+0928 u+094D 60 | u+0926 u+0940 u+0915 u+094D u+092F u+094B u+0902 u+0928 u+0939 61 | u+0940 u+0902 u+092C u+094B u+0932 u+0938 u+0915 u+0924 u+0947 62 | u+0939 u+0948 u+0902 63 | ]]), 'i1baa7eci9glrd9b2ae1bj0hfcgg6iyaf8o0a1dig0cd') 64 | end) 65 | 66 | test("Japanese (kanji and hiragana)", function() 67 | assert_equal(pe(prepare[[ 68 | u+306A u+305C u+307F u+3093 u+306A u+65E5 u+672C u+8A9E u+3092 69 | u+8A71 u+3057 u+3066 u+304F u+308C u+306A u+3044 u+306E u+304B 70 | ]]), 'n8jok5ay5dzabd5bym9f0cm5685rrjetr6pdxa') 71 | end) 72 | 73 | test("Korean (Hangul syllables)", function() 74 | assert_equal(pe(prepare[[ 75 | u+C138 u+ACC4 u+C758 u+BAA8 u+B4E0 u+C0AC u+B78C u+B4E4 u+C774 76 | u+D55C u+AD6D u+C5B4 u+B97C u+C774 u+D574 u+D55C u+B2E4 u+BA74 77 | u+C5BC u+B9C8 u+B098 u+C88B u+C744 u+AE4C 78 | ]]), '989aomsvi5e83db1d2a355cv1e0vak1dwrv93d5xbh15a0dt30a5jpsd879ccm6fea98c') 79 | end) 80 | 81 | test("Russian (Cyrillic)", function() 82 | assert_equal(pe(prepare[[ 83 | U+043F u+043E u+0447 u+0435 u+043C u+0443 u+0436 u+0435 u+043E 84 | u+043D u+0438 u+043D u+0435 u+0433 u+043E u+0432 u+043E u+0440 85 | u+044F u+0442 u+043F u+043E u+0440 u+0443 u+0441 u+0441 u+043A 86 | u+0438 87 | ]]), 'b1abfaaepdrnnbgefbadotcwatmq2g4l') 88 | end) 89 | 90 | test("Spanish", function() 91 | assert_equal(pe(prepare[[ 92 | U+0050 u+006F u+0072 u+0071 u+0075 u+00E9 u+006E u+006F u+0070 93 | u+0075 u+0065 u+0064 u+0065 u+006E u+0073 u+0069 u+006D u+0070 94 | u+006C u+0065 u+006D u+0065 u+006E u+0074 u+0065 u+0068 u+0061 95 | u+0062 u+006C u+0061 u+0072 u+0065 u+006E U+0045 u+0073 u+0070 96 | u+0061 u+00F1 u+006F u+006C 97 | ]]), 'PorqunopuedensimplementehablarenEspaol-fmd56a') 98 | end) 99 | 100 | test("Vietnamese", function() 101 | assert_equal(pe(prepare[[ 102 | U+0054 u+1EA1 u+0069 u+0073 u+0061 u+006F u+0068 u+1ECD u+006B 103 | u+0068 u+00F4 u+006E u+0067 u+0074 u+0068 u+1EC3 u+0063 u+0068 104 | u+1EC9 u+006E u+00F3 u+0069 u+0074 u+0069 u+1EBF u+006E u+0067 105 | U+0056 u+0069 u+1EC7 u+0074 106 | ]]), 'TisaohkhngthchnitingVit-kjcr8268qyxafd2f1b9g') 107 | end) 108 | 109 | test("3年B組金八先生", function() 110 | assert_equal(pe(prepare[[ 111 | u+0033 u+5E74 U+0042 u+7D44 u+91D1 u+516B u+5148 u+751F 112 | ]]), '3B-ww4c5e180e575a65lsy2b') 113 | end) 114 | 115 | test("安室奈美恵-with-SUPER-MONKEYS", function() 116 | assert_equal(pe(prepare[[ 117 | u+5B89 u+5BA4 u+5948 u+7F8E u+6075 u+002D u+0077 u+0069 u+0074 118 | u+0068 u+002D U+0053 U+0055 U+0050 U+0045 U+0052 u+002D U+004D 119 | U+004F U+004E U+004B U+0045 U+0059 U+0053 120 | ]]), '-with-SUPER-MONKEYS-pc58ag80a8qai00g7n9n') 121 | end) 122 | 123 | test("Hello-Another-Way-それぞれの場所", function() 124 | assert_equal(pe(prepare[[ 125 | U+0048 u+0065 u+006C u+006C u+006F u+002D U+0041 u+006E u+006F 126 | u+0074 u+0068 u+0065 u+0072 u+002D U+0057 u+0061 u+0079 u+002D 127 | u+305D u+308C u+305E u+308C u+306E u+5834 u+6240 128 | ]]), 'Hello-Another-Way--fc4qua05auwb3674vfr0b') 129 | end) 130 | 131 | test("ひとつ屋根の下2", function() 132 | assert_equal(pe(prepare[[ 133 | u+3072 u+3068 u+3064 u+5C4B u+6839 u+306E u+4E0B u+0032 134 | ]]), '2-u9tlzr9756bt3uc0v') 135 | end) 136 | 137 | test("MajiでKoiする5秒前", function() 138 | assert_equal(pe(prepare[[ 139 | U+004D u+0061 u+006A u+0069 u+3067 U+004B u+006F u+0069 u+3059 140 | u+308B u+0035 u+79D2 u+524D 141 | ]]), 'MajiKoi5-783gue6qz075azm5e') 142 | end) 143 | 144 | test("パフィーdeルンバ", function() 145 | assert_equal(pe(prepare[[ 146 | u+30D1 u+30D5 u+30A3 u+30FC u+0064 u+0065 u+30EB u+30F3 u+30D0 147 | ]]), 'de-jg4avhby1noc0d') 148 | end) 149 | 150 | test("そのスピードで", function() 151 | assert_equal(pe(prepare[[ 152 | u+305D u+306E u+30B9 u+30D4 u+30FC u+30C9 u+3067 153 | ]]), 'd9juau41awczczp') 154 | end) 155 | 156 | test("-> $1.00 <-", function() 157 | assert_equal(pe(prepare[[ 158 | u+002D u+003E u+0020 u+0024 u+0031 u+002E u+0030 u+0030 u+0020 159 | u+003C u+002D 160 | ]]), '-> $1.00 <--') 161 | end) 162 | end) 163 | 164 | context("Decoding", function() 165 | test("Arabic (Egyptian)", function() 166 | assert_equal(pd('egbpdaj6bu4bxfgehfvwxn'), prepare[[ 167 | u+0644 u+064A u+0647 u+0645 u+0627 u+0628 u+062A u+0643 u+0644 168 | u+0645 u+0648 u+0634 u+0639 u+0631 u+0628 u+064A u+061F 169 | ]]) 170 | end) 171 | 172 | test("Chinese (simplified)", function() 173 | assert_equal(pd('ihqwcrb4cv8a8dqg056pqjye'), prepare[[ 174 | u+4ED6 u+4EEC u+4E3A u+4EC0 u+4E48 u+4E0D u+8BF4 u+4E2D u+6587 175 | ]]) 176 | end) 177 | 178 | test("Chinese (traditional)", function() 179 | assert_equal(pd('ihqwctvzc91f659drss3x8bo0yb'), prepare[[ 180 | u+4ED6 u+5011 u+7232 u+4EC0 u+9EBD u+4E0D u+8AAA u+4E2D u+6587 181 | ]]) 182 | end) 183 | 184 | test("Czech", function() 185 | assert_equal(pd('Proprostnemluvesky-uyb24dma41a'), prepare[[ 186 | U+0050 u+0072 u+006F u+010D u+0070 u+0072 u+006F u+0073 u+0074 187 | u+011B u+006E u+0065 u+006D u+006C u+0075 u+0076 u+00ED u+010D 188 | u+0065 u+0073 u+006B u+0079 189 | ]]) 190 | end) 191 | 192 | test("Hebrew", function() 193 | assert_equal(pd('4dbcagdahymbxekheh6e0a7fei0b'), prepare[[ 194 | u+05DC u+05DE u+05D4 u+05D4 u+05DD u+05E4 u+05E9 u+05D5 u+05D8 195 | u+05DC u+05D0 u+05DE u+05D3 u+05D1 u+05E8 u+05D9 u+05DD u+05E2 196 | u+05D1 u+05E8 u+05D9 u+05EA 197 | ]]) 198 | end) 199 | 200 | test("Hindi (Devanagari)", function() 201 | assert_equal(pd('i1baa7eci9glrd9b2ae1bj0hfcgg6iyaf8o0a1dig0cd'), prepare[[ 202 | u+092F u+0939 u+0932 u+094B u+0917 u+0939 u+093F u+0928 u+094D 203 | u+0926 u+0940 u+0915 u+094D u+092F u+094B u+0902 u+0928 u+0939 204 | u+0940 u+0902 u+092C u+094B u+0932 u+0938 u+0915 u+0924 u+0947 205 | u+0939 u+0948 u+0902 206 | ]]) 207 | end) 208 | 209 | test("Japanese (kanji and hiragana)", function() 210 | assert_equal(pd('n8jok5ay5dzabd5bym9f0cm5685rrjetr6pdxa'), prepare[[ 211 | u+306A u+305C u+307F u+3093 u+306A u+65E5 u+672C u+8A9E u+3092 212 | u+8A71 u+3057 u+3066 u+304F u+308C u+306A u+3044 u+306E u+304B 213 | ]]) 214 | end) 215 | 216 | test("Korean (Hangul syllables)", function() 217 | assert_equal(pd('989aomsvi5e83db1d2a355cv1e0vak1dwrv93d5xbh15a0dt30a5jpsd879ccm6fea98c'), prepare[[ 218 | u+C138 u+ACC4 u+C758 u+BAA8 u+B4E0 u+C0AC u+B78C u+B4E4 u+C774 219 | u+D55C u+AD6D u+C5B4 u+B97C u+C774 u+D574 u+D55C u+B2E4 u+BA74 220 | u+C5BC u+B9C8 u+B098 u+C88B u+C744 u+AE4C 221 | ]]) 222 | end) 223 | 224 | test("Russian (Cyrillic)", function() 225 | assert_equal(pd('b1abfaaepdrnnbgefbaDotcwatmq2g4l'), prepare[[ 226 | U+043F u+043E u+0447 u+0435 u+043C u+0443 u+0436 u+0435 u+043E 227 | u+043D u+0438 u+043D u+0435 u+0433 u+043E u+0432 u+043E u+0440 228 | u+044F u+0442 u+043F u+043E u+0440 u+0443 u+0441 u+0441 u+043A 229 | u+0438 230 | ]]) 231 | end) 232 | 233 | test("Spanish", function() 234 | assert_equal(pd('PorqunopuedensimplementehablarenEspaol-fmd56a'), prepare[[ 235 | U+0050 u+006F u+0072 u+0071 u+0075 u+00E9 u+006E u+006F u+0070 236 | u+0075 u+0065 u+0064 u+0065 u+006E u+0073 u+0069 u+006D u+0070 237 | u+006C u+0065 u+006D u+0065 u+006E u+0074 u+0065 u+0068 u+0061 238 | u+0062 u+006C u+0061 u+0072 u+0065 u+006E U+0045 u+0073 u+0070 239 | u+0061 u+00F1 u+006F u+006C 240 | ]]) 241 | end) 242 | 243 | test("Vietnamese", function() 244 | assert_equal(pd('TisaohkhngthchnitingVit-kjcr8268qyxafd2f1b9g'), prepare[[ 245 | U+0054 u+1EA1 u+0069 u+0073 u+0061 u+006F u+0068 u+1ECD u+006B 246 | u+0068 u+00F4 u+006E u+0067 u+0074 u+0068 u+1EC3 u+0063 u+0068 247 | u+1EC9 u+006E u+00F3 u+0069 u+0074 u+0069 u+1EBF u+006E u+0067 248 | U+0056 u+0069 u+1EC7 u+0074 249 | ]]) 250 | end) 251 | 252 | test("3年B組金八先生", function() 253 | assert_equal(pd('3B-ww4c5e180e575a65lsy2b'), prepare[[ 254 | u+0033 u+5E74 U+0042 u+7D44 u+91D1 u+516B u+5148 u+751F 255 | ]]) 256 | end) 257 | 258 | test("安室奈美恵-with-SUPER-MONKEYS", function() 259 | assert_equal(pd('-with-SUPER-MONKEYS-pc58ag80a8qai00g7n9n'), prepare[[ 260 | u+5B89 u+5BA4 u+5948 u+7F8E u+6075 u+002D u+0077 u+0069 u+0074 261 | u+0068 u+002D U+0053 U+0055 U+0050 U+0045 U+0052 u+002D U+004D 262 | U+004F U+004E U+004B U+0045 U+0059 U+0053 263 | ]]) 264 | end) 265 | 266 | test("Hello-Another-Way-それぞれの場所", function() 267 | assert_equal(pd('Hello-Another-Way--fc4qua05auwb3674vfr0b'), prepare[[ 268 | U+0048 u+0065 u+006C u+006C u+006F u+002D U+0041 u+006E u+006F 269 | u+0074 u+0068 u+0065 u+0072 u+002D U+0057 u+0061 u+0079 u+002D 270 | u+305D u+308C u+305E u+308C u+306E u+5834 u+6240 271 | ]]) 272 | end) 273 | 274 | test("ひとつ屋根の下2", function() 275 | assert_equal(pd('2-u9tlzr9756bt3uc0v'), prepare[[ 276 | u+3072 u+3068 u+3064 u+5C4B u+6839 u+306E u+4E0B u+0032 277 | ]]) 278 | end) 279 | 280 | test("MajiでKoiする5秒前", function() 281 | assert_equal(pd('MajiKoi5-783gue6qz075azm5e'), prepare[[ 282 | U+004D u+0061 u+006A u+0069 u+3067 U+004B u+006F u+0069 u+3059 283 | u+308B u+0035 u+79D2 u+524D 284 | ]]) 285 | end) 286 | 287 | test("パフィーdeルンバ", function() 288 | assert_equal(pd('de-jg4avhby1noc0d'), prepare[[ 289 | u+30D1 u+30D5 u+30A3 u+30FC u+0064 u+0065 u+30EB u+30F3 u+30D0 290 | ]]) 291 | end) 292 | 293 | test("そのスピードで", function() 294 | assert_equal(pd('d9juau41awczczp'), prepare[[ 295 | u+305D u+306E u+30B9 u+30D4 u+30FC u+30C9 u+3067 296 | ]]) 297 | end) 298 | 299 | test("-> $1.00 <-", function() 300 | assert_equal(pd('-> $1.00 <--'), prepare[[ 301 | u+002D u+003E u+0020 u+0024 u+0031 u+002E u+0030 u+0030 u+0020 302 | u+003C u+002D 303 | ]]) 304 | end) 305 | end) 306 | 307 | context("Hyphens", function() 308 | test("first letter first label", function() 309 | assert_nil(ie'-example') 310 | end) 311 | 312 | test("first letter second label", function() 313 | assert_nil(ie'www.-example') 314 | end) 315 | 316 | test("last letter first label", function() 317 | assert_nil(ie'example-') 318 | end) 319 | 320 | test("last letter second label", function() 321 | assert_nil(ie'www.example-') 322 | end) 323 | 324 | test("second letter first label", function() 325 | assert_equal(ie'e-xample', 'e-xample') 326 | end) 327 | 328 | test("second letter second label", function() 329 | assert_equal(ie'www.e-xample', 'www.e-xample') 330 | end) 331 | 332 | test("second last letter first label", function() 333 | assert_equal(ie'exampl-e', 'exampl-e') 334 | end) 335 | 336 | test("second last letter second label", function() 337 | assert_equal(ie'www.exampl-e', 'www.exampl-e') 338 | end) 339 | end) 340 | end) 341 | -------------------------------------------------------------------------------- /tsc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env lua 2 | package.path = './?.lua;' .. package.path 3 | pcall(require, "luarocks.require") 4 | require 'telescope' 5 | pcall(require, "shake") 6 | 7 | local function luacov_report() 8 | local luacov = require("luacov.stats") 9 | local data = luacov.load_stats() 10 | if not data then 11 | print("Could not load stats file "..luacov.statsfile..".") 12 | print("Run your Lua program with -lluacov and then rerun luacov.") 13 | os.exit(1) 14 | end 15 | local report = io.open("coverage.html", "w") 16 | report:write('', "\n") 17 | report:write([[ 18 | 19 | 20 | 21 | Luacov Coverage Report 22 | 23 | 38 | 39 | 40 |
41 |

Luacov Code Coverage Report

42 | ]]) 43 | report:write("

Generated on ", os.date(), "

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

", filename, "

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