• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

FourierTransformer / tested / 29219165366

13 Jul 2026 02:16AM UTC coverage: 89.815% (+0.2%) from 89.571%
29219165366

push

github

web-flow
Merge a7363eedb into 2d7f05a6f

245 of 253 new or added lines in 12 files covered. (96.84%)

73 existing lines in 4 files now uncovered.

1702 of 1895 relevant lines covered (89.82%)

5192.1 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

70.37
/build/tested/cli.lua
1
local argparse = require("argparse")
20✔
2
local lfs = require("lfs")
20✔
3

4
local logging = require("tested.libs.logging")
20✔
5
local logger = logging.get_logger("tested.cli")
20✔
6
local util = require("tested.util")
20✔
7

8

9

10
local cli = { CLIOptions = {} }
20✔
11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57
local cli_to_display = {
20✔
58
   ["skip"] = "SKIP",
15✔
59
   ["pass"] = "PASS",
15✔
60
   ["fail"] = "FAIL",
15✔
61
   ["exception"] = "EXCEPTION",
15✔
62
   ["unknown"] = "UNKNOWN",
15✔
63
   ["unexpected"] = "UNEXPECTED",
15✔
64

65
}
66

67
function cli.parse_args(version)
20✔
68
   local parser = argparse("tested", "A Lua/Teal Unit Testing Framework", "For more info see https://fouriertransformer.github.io/tested")
20✔
69

70
   parser:flag("-c --coverage"):
30✔
71
   description("Enable code coverage - will generate luacov.stats.out (default: not-set)"):
20✔
72
   default(false)
20✔
73

74
   parser:flag("-r --random"):
30✔
75
   description("Randomize the order of the tests (default: not-set)"):
20✔
76
   default(false)
20✔
77

78
   parser:option("-F --filter"):
25✔
79
   description("Only run tests whose name matches this Lua pattern (default: not-set)")
20✔
80

81
   parser:option("-t --tags"):
25✔
82
   description("Only run tests matching a tag expression, e.g. 'integration' or '(unit or integration) and not slow' (default: not-set)")
20✔
83

84
   parser:option("-s --show"):
35✔
85
   description("What test results to display (default: '-s fail -s exception -s unknown')"):
20✔
86
   choices({ "all", "valid", "invalid", "skip", "pass", "fail", "exception", "unknown", "expected", "unexpected" }):
20✔
87
   count("*")
20✔
88

89
   parser:mutex(
40✔
90
   parser:option("-f --display-format"):
35✔
91
   description("What format to output the results in (default: 'terminal')"):
20✔
92
   choices({ "terminal", "plain", "tap" }):
20✔
93
   default("terminal"),
20✔
94
   parser:option("-z --custom-formatter"):
25✔
95
   description("File that loads a custom formatter to use for terminal output"))
20✔
96

97

98
   parser:option("-o --output-file"):
30✔
99
   description("Output file to save test results in (currently supported extensions: '.txt' and '.json')"):
20✔
100
   count("*")
20✔
101

102
   parser:option("-n --instances"):
35✔
103
   description("Set the number of concurrent tests to run (default: 4). Due to threading behaviour, this is now a noop. All tests run seqeuntially."):
20✔
104
   default(4):
20✔
105
   convert(tonumber)
20✔
106

107
   parser:option("-x --language-handler"):
30✔
108
   description("File that loads custom language that is Lua-compatible"):
20✔
109
   count("*")
20✔
110

111
   parser:option("-d --debug"):
35✔
112
   description("Set the log level - mostly for debugging issues with tested (default: 'WARNING')"):
20✔
113
   choices({ "DEBUG", "INFO", "WARNING" }):
20✔
114
   default("WARNING")
20✔
115

116
   parser:flag("--version"):
30✔
117
   description("Show version information"):
20✔
118
   action(function() print(version); os.exit(0) end)
20✔
119

120
   parser:flag("-p --process"):
35✔
121
   description("Internal flag used to indicate that this test is running as a process"):
20✔
122
   default(false):
20✔
123
   hidden(true)
20✔
124

125
   parser:argument("paths", "Path(s) to directories or files with tests to run (default: 'tests')"):
25✔
126
   args("*")
20✔
127

128
   logger:info("Parsing Arguments...")
20✔
129

130
   local args = parser:parse()
20✔
131
   return args
20✔
132
end
133

134
function cli.set_defaults(args)
20✔
135
   logger:info("Setting Defaults...")
20✔
136
   if #args.show == 0 then
20✔
137
      args.show = { "fail", "exception", "unknown", "unexpected" }
20✔
138
      args.specified_show = false
20✔
139
   else
UNCOV
140
      args.specified_show = true
×
141
   end
142
   if #args.paths == 0 then args.paths = { "tests" } end
20✔
143
   args.test_files = {}
20✔
144
   args.test_directories = {}
20✔
145

146
   local show_all = false
20✔
147
   for _, display_option in ipairs(args.show) do if display_option == "all" then show_all = true; break end end
100✔
148
   if show_all then args.show = { "skip", "pass", "fail", "exception", "unknown", "expected", "unexpected" } end
20✔
149

150
   if #args.output_file > 0 then
20✔
UNCOV
151
      for _, output_file in ipairs(args.output_file) do
×
UNCOV
152
         if not (util.get_file_extension(output_file) == ".txt" or util.get_file_extension(output_file) == ".json") then
×
UNCOV
153
            error("The given output file does not have a supported file extension: '" .. output_file .. "'. Supported file extensions are: '.txt', '.json'", 0)
×
154
         end
155
      end
156
   end
157
end
158

159
function cli.validate_args(args)
20✔
160
   logger:info("Validating args...")
20✔
161
   for _, path in ipairs(args.paths) do
40✔
162
      local info, err = lfs.attributes(path)
20✔
163
      if err then error("The file or directory '" .. path .. "' does not appear to exist. Unable to run tests") end
20✔
164
      if not (info.mode == "directory" or info.mode == "file") then error("tested requires the paths passed in to be a directory or file", 0) end
20✔
165
      if info.mode == "directory" then table.insert(args.test_directories, path) end
20✔
166
      if info.mode == "file" then table.insert(args.test_files, path) end
20✔
167
   end
168
   if args.filter then
20✔
169
      local ok, err = pcall(string.find, "", args.filter)
×
170
      if not ok then
×
171
         error("Invalid --filter pattern '" .. args.filter .. "': " .. tostring(err), 0)
×
172
      end
173
   end
174
   if args.tags then
20✔
UNCOV
175
      if args.tags:match("[^a-zA-Z0-9_ ()]") then
×
UNCOV
176
         error("Invalid --tags expression: only letters, digits, underscores, spaces, and parentheses are allowed", 0)
×
177
      end
UNCOV
178
      local lua_expr = (args.tags:gsub("([a-zA-Z_][a-zA-Z0-9_]*)", function(word)
×
UNCOV
179
         if word == "and" or word == "or" or word == "not" then return word end
×
UNCOV
180
         return 'tags["' .. word .. '"]'
×
181
      end))
UNCOV
182
      local tag_filter = "local tags = ... \nreturn " .. lua_expr
×
183

184

UNCOV
185
      local string_loader = loadstring or load
×
UNCOV
186
      local loaded = string_loader(tag_filter)
×
187
      if not loaded then
×
188
         error("Invalid --tags expression '" .. args.tags .. "': Be sure to use boolean expressions ('or', 'and', 'not', etc) when combining tags", 0)
×
189
      end
UNCOV
190
      args.tags_filter = loaded
×
191
   end
192

193
   if args.custom_formatter then
20✔
194
      if not (util.is_valid_filename(args.custom_formatter) or util.is_valid_lua_module_name(args.custom_formatter)) then
×
UNCOV
195
         error("Invalid --custom-formatter '" .. args.custom_formatter .. "'. It does not appear to be a valid filepath nor Lua module name")
×
196
      end
197
   end
198

199
   if args.language_handler then
20✔
200
      for _, language_handler in ipairs(args.language_handler) do
20✔
UNCOV
201
         if not (util.is_valid_filename(language_handler) or util.is_valid_lua_module_name(language_handler)) then
×
UNCOV
202
            error("Invalid --format-handler '" .. language_handler .. "'. It does not appear to be a valid filepath nor Lua module name")
×
203
         end
204
      end
205
   end
206
end
207

208
function cli.display_types(options)
20✔
209
   local to_display = {}
440✔
210
   for _, cli_option in ipairs(options) do
2,200✔
211
      if cli_to_display[cli_option] then
1,760✔
212
         to_display[cli_to_display[cli_option]] = true
1,760✔
213
         if cli_option == "skip" then
1,760✔
UNCOV
214
            to_display["SKIP"] = true
×
215
         end
216
      else
UNCOV
217
         if cli_option == "invalid" then
×
UNCOV
218
            to_display["EXCEPTION"] = true
×
219
            to_display["UNKNOWN"] = true
×
220
            to_display["TIMEOUT"] = true
×
UNCOV
221
            to_display["UNEXPECTED"] = true
×
UNCOV
222
         elseif cli_option == "valid" then
×
UNCOV
223
            to_display["PASS"] = true
×
UNCOV
224
            to_display["SKIP"] = true
×
UNCOV
225
            to_display["FILTERED"] = true
×
UNCOV
226
            to_display["FAIL"] = true
×
UNCOV
227
            to_display["EXPECTED_FAIL"] = true
×
UNCOV
228
            to_display["EXPECTED_EXCEPTION"] = true
×
UNCOV
229
            to_display["EXPECTED_UNKNOWN"] = true
×
UNCOV
230
         elseif cli_option == "expected" then
×
UNCOV
231
            to_display["EXPECTED_FAIL"] = true
×
232
            to_display["EXPECTED_EXCEPTION"] = true
×
UNCOV
233
            to_display["EXPECTED_UNKNOWN"] = true
×
234
         end
235
      end
236
   end
237
   return to_display
440✔
238
end
239

240
return cli
20✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc