mirror of
https://github.com/pesde-pkg/scripts.git
synced 2024-12-12 07:00:35 +00:00
feat: test setup using frktest & more
* Sets up a test runner system and includes unit tests for existing generators using Rojo's test files. * Configure Luau analysis and disables unnecessary lints. * Make sync config generator accept a third options argument with a `force` option to generate configs even when there is a `default.project.json` present in the `projectDir`.
This commit is contained in:
parent
5f068ba1b8
commit
83ab6bf97f
13 changed files with 277 additions and 30 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
*_packages/
|
||||||
|
pesde.lock
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
[submodule "test-files/rojo"]
|
||||||
|
path = test-files/rojo
|
||||||
|
url = https://github.com/rojo-rbx/rojo.git
|
7
.luaurc
Normal file
7
.luaurc
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"languageMode": "nonstrict",
|
||||||
|
"lint": {
|
||||||
|
"*": true,
|
||||||
|
"TableOperations": false
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,19 +1,17 @@
|
||||||
local function enter(fn: (args: { string }) -> number?): never
|
local function enter(fn: (args: { string }) -> number?): never
|
||||||
local process = require("@lune/process")
|
local process = require("@lune/process")
|
||||||
local stdio = require("@lune/stdio")
|
local stdio = require("@lune/stdio")
|
||||||
|
|
||||||
local startTime = os.clock()
|
local startTime = os.clock()
|
||||||
local exitCode = fn(table.clone(process.args))
|
local exitCode = fn(table.clone(process.args))
|
||||||
|
|
||||||
stdio.write(
|
stdio.write(`done in {stdio.style("dim")}{string.format("%.2fs", os.clock() - startTime)}{stdio.style("reset")}!\n`)
|
||||||
`done in {stdio.style("dim")}{string.format("%.2fs", os.clock() - startTime)}{stdio.style("reset")}!\n`
|
|
||||||
)
|
|
||||||
|
|
||||||
return process.exit(exitCode)
|
return process.exit(exitCode)
|
||||||
end
|
end
|
||||||
|
|
||||||
return enter(function(args: { string }): number?
|
return enter(function(args: { string }): number?
|
||||||
local ok, _ = require("../src").generators.rojo.syncConfig(table.remove(args, 1), args, true)
|
local ok, _ = require("../src").generators.rojo.syncConfig(table.remove(args, 1), args, { writeToFile = true })
|
||||||
|
|
||||||
return tonumber(ok)
|
return tonumber(ok)
|
||||||
end)
|
end)
|
||||||
|
|
74
.lune/tests/init.luau
Normal file
74
.lune/tests/init.luau
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
--> Run tests using frktest runner
|
||||||
|
|
||||||
|
local fs = require("@lune/fs")
|
||||||
|
local process = require("@lune/process")
|
||||||
|
|
||||||
|
local frktest = require("../../lune_packages/frktest")
|
||||||
|
local reporter = require("./reporter")
|
||||||
|
|
||||||
|
-- HACK: Cast require to allow for dynamic paths in strict mode
|
||||||
|
-- A more proper solution would be to use luau.load instead, but
|
||||||
|
-- frktest requires its global state to be modified by test suites
|
||||||
|
local require = require :: (
|
||||||
|
path: string
|
||||||
|
) -> (
|
||||||
|
test: typeof(setmetatable(
|
||||||
|
{} :: {
|
||||||
|
case: (name: string, fn: () -> nil) -> (),
|
||||||
|
suite: (name: string, fn: () -> ()) -> (),
|
||||||
|
},
|
||||||
|
{ __index = frktest.test }
|
||||||
|
))
|
||||||
|
) -> ()
|
||||||
|
|
||||||
|
local function discoverTests(dir: string)
|
||||||
|
local tests = {}
|
||||||
|
for _, file in fs.readDir(dir) do
|
||||||
|
local fullPath = `{dir}/{file}`
|
||||||
|
|
||||||
|
-- Look for files ending in `.spec.luau` as tests
|
||||||
|
if fs.isFile(fullPath) and string.sub(file, -10) == ".spec.luau" then
|
||||||
|
table.insert(tests, fullPath)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Recurse for directories
|
||||||
|
if fs.isDir(fullPath) then
|
||||||
|
local moreTests = discoverTests(fullPath)
|
||||||
|
|
||||||
|
-- Why are the indices starting at 0???? What????
|
||||||
|
table.move(moreTests, 0, #moreTests, #tests, tests)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return tests
|
||||||
|
end
|
||||||
|
|
||||||
|
local allowedTests = process.args
|
||||||
|
for _, test in discoverTests("src") do
|
||||||
|
-- If we are given any arguments, we only run those tests, otherwise,
|
||||||
|
-- we run all the tests
|
||||||
|
|
||||||
|
-- So, to include only a certain set of test files, you can provide either
|
||||||
|
-- the full path to the test file (with or without the extension) or the test
|
||||||
|
-- file name
|
||||||
|
local withoutExt = string.sub(test, 1, -6)
|
||||||
|
local isAllowed = #process.args == 0
|
||||||
|
or table.find(allowedTests, `tests/{test}`)
|
||||||
|
or table.find(allowedTests, withoutExt)
|
||||||
|
or table.find(allowedTests, `tests/{withoutExt}`)
|
||||||
|
|
||||||
|
local constructors = {
|
||||||
|
case = frktest.test.case,
|
||||||
|
suite = frktest.test.suite,
|
||||||
|
}
|
||||||
|
|
||||||
|
if not isAllowed then
|
||||||
|
constructors.case = frktest.test.skip.case
|
||||||
|
constructors.suite = frktest.test.skip.suite
|
||||||
|
end
|
||||||
|
|
||||||
|
require(`../../{test}`)(setmetatable(constructors, { __index = frktest.test }))
|
||||||
|
end
|
||||||
|
|
||||||
|
reporter.init()
|
||||||
|
process.exit(tonumber(frktest.run()))
|
45
.lune/tests/reporter.luau
Normal file
45
.lune/tests/reporter.luau
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
local stdio = require("@lune/stdio")
|
||||||
|
|
||||||
|
local frktest = require("../../lune_packages/frktest")
|
||||||
|
local Reporter = frktest._reporters.lune_console_reporter
|
||||||
|
|
||||||
|
local STYLE = table.freeze({
|
||||||
|
suite = function(name: string)
|
||||||
|
return `{stdio.style("bold")}{stdio.color("purple")}SUITE{stdio.style("reset")} {name}`
|
||||||
|
end,
|
||||||
|
|
||||||
|
report = function(name: string, state: "run" | "success" | "error" | "skip")
|
||||||
|
local state_color: stdio.Color = if state == "run"
|
||||||
|
then "white"
|
||||||
|
elseif state == "success" then "green"
|
||||||
|
elseif state == "error" then "red"
|
||||||
|
elseif state == "skip" then "yellow"
|
||||||
|
else error("Invalid test state")
|
||||||
|
return ` {stdio.style("bold")}{stdio.color(state_color)}{if state == "skip" then "SKIP" else "TEST"}{stdio.style(
|
||||||
|
"reset"
|
||||||
|
)} {name}`
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
local ReporterExt = {}
|
||||||
|
function ReporterExt.init()
|
||||||
|
frktest.test.on_suite_enter(function(suite)
|
||||||
|
print(STYLE.suite(suite.name))
|
||||||
|
end)
|
||||||
|
|
||||||
|
frktest.test.on_suite_leave(function()
|
||||||
|
stdio.write("\n")
|
||||||
|
end)
|
||||||
|
|
||||||
|
frktest.test.on_test_leave(function(test)
|
||||||
|
print(STYLE.report(test.name, if test.failed then "error" else "success"))
|
||||||
|
end)
|
||||||
|
|
||||||
|
frktest.test.on_test_skipped(function(test)
|
||||||
|
print(STYLE.report(test.name, "skip"))
|
||||||
|
end)
|
||||||
|
|
||||||
|
Reporter.init()
|
||||||
|
end
|
||||||
|
|
||||||
|
return setmetatable(ReporterExt, { __index = Reporter })
|
|
@ -5,7 +5,7 @@ description = "Scripts and other utilities for use with pesde"
|
||||||
authors = ["dai <contact@daimond113.com> (https://www.daimond113.com/)", "Erica Marigold <hi@devcomp.xyz>"]
|
authors = ["dai <contact@daimond113.com> (https://www.daimond113.com/)", "Erica Marigold <hi@devcomp.xyz>"]
|
||||||
repository = "https://github.com/pesde-pkg/scripts"
|
repository = "https://github.com/pesde-pkg/scripts"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
includes = ["src/**/*.luau", "README.md", "LICENSE.md"]
|
includes = ["src/**/*.luau", "pesde.toml", "README.md", "LICENSE.md"]
|
||||||
|
|
||||||
[target]
|
[target]
|
||||||
environment = "lune"
|
environment = "lune"
|
||||||
|
@ -13,3 +13,6 @@ lib = "src/init.luau"
|
||||||
|
|
||||||
[indices]
|
[indices]
|
||||||
default = "https://github.com/daimond113/pesde-index"
|
default = "https://github.com/daimond113/pesde-index"
|
||||||
|
|
||||||
|
[dev_dependencies]
|
||||||
|
frktest = { name = "itsfrank/frktest", version = "^0.0.2" }
|
||||||
|
|
|
@ -16,14 +16,10 @@ local PATH_ACTION_MAP: { [string]: (dir: string) -> number? } = {
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["init.lua"] = function()
|
["init.lua"] = function()
|
||||||
return stdio.write(
|
return stdio.write(serde.encode("json", { filePaths = { "init.lua" } }, false))
|
||||||
serde.encode("json", { filePaths = { "init.lua" } }, false)
|
|
||||||
)
|
|
||||||
end,
|
end,
|
||||||
["init.luau"] = function()
|
["init.luau"] = function()
|
||||||
return stdio.write(
|
return stdio.write(serde.encode("json", { filePaths = { "init.luau" } }, false))
|
||||||
serde.encode("json", { filePaths = { "init.luau" } }, false)
|
|
||||||
)
|
|
||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,9 +28,10 @@ local PATH_ACTION_MAP: { [string]: (dir: string) -> number? } = {
|
||||||
|
|
||||||
--- ## Errors
|
--- ## Errors
|
||||||
--- * The current process lacks permissions to a file
|
--- * The current process lacks permissions to a file
|
||||||
|
--- * Failure to spawn `rojo` command
|
||||||
--- * Any I/O error occurs
|
--- * Any I/O error occurs
|
||||||
return function(packageDirectory: string?): boolean
|
return function(packageDirectory: string?): boolean
|
||||||
packageDirectory = packageDirectory or process.cwd
|
packageDirectory = packageDirectory or process.cwd
|
||||||
|
|
||||||
-- We go through the action mappings in order of priority and check for the
|
-- We go through the action mappings in order of priority and check for the
|
||||||
-- file predicates, if present, we execute the action and report our status
|
-- file predicates, if present, we execute the action and report our status
|
||||||
|
|
87
src/generators/rojo/sourcemap.spec.luau
Normal file
87
src/generators/rojo/sourcemap.spec.luau
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
local fs = require("@lune/fs")
|
||||||
|
local luau = require("@lune/luau")
|
||||||
|
local process = require("@lune/process")
|
||||||
|
local regex = require("@lune/regex")
|
||||||
|
local serde = require("@lune/serde")
|
||||||
|
|
||||||
|
local frktest = require("../../../lune_packages/frktest")
|
||||||
|
local check = frktest.assert.check
|
||||||
|
|
||||||
|
local TEST_PROJECTS_DIR = "./test-files/rojo/test-projects"
|
||||||
|
local TEST_PROJECT_EXCLUDES = {
|
||||||
|
"json_model",
|
||||||
|
"bad_json_model",
|
||||||
|
"plugins",
|
||||||
|
"legacy-0.5.x-unknown-names",
|
||||||
|
}
|
||||||
|
|
||||||
|
local BUILTIN_PATCHES: {
|
||||||
|
[string]: { [string]: (...any) -> ...any? },
|
||||||
|
} = {
|
||||||
|
["@lune/process"] = {
|
||||||
|
spawn = function(program: string, params: { string }, options: process.SpawnOptions): process.SpawnResult
|
||||||
|
local patchedOptions: process.SpawnOptions = options or {}
|
||||||
|
patchedOptions.stdio = "default"
|
||||||
|
|
||||||
|
local result = process.spawn(program, params, patchedOptions)
|
||||||
|
|
||||||
|
-- First we make sure the command exited properly
|
||||||
|
assert(result.ok, `Expected \`rojo\` command to not error, got:\n{string.rep(" ", 6)}{result.stderr}`)
|
||||||
|
|
||||||
|
-- We also make sure that the output JSON was valid
|
||||||
|
serde.decode("json", result.stdout)
|
||||||
|
|
||||||
|
return result
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
["@lune/stdio"] = {
|
||||||
|
write = function(msg: string)
|
||||||
|
-- Only make sure output JSON is valid
|
||||||
|
serde.decode("json", msg)
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
local function requireWithPatches(path: string)
|
||||||
|
for builtin, patch in BUILTIN_PATCHES do
|
||||||
|
if path == builtin then
|
||||||
|
return setmetatable(patch, { __index = require(builtin) })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return require(path)
|
||||||
|
end
|
||||||
|
|
||||||
|
return function(test: typeof(frktest.test))
|
||||||
|
test.suite("Generates Rojo sourcemaps for test projects successfully", function()
|
||||||
|
for _, file in fs.readDir(TEST_PROJECTS_DIR) do
|
||||||
|
if table.find(TEST_PROJECT_EXCLUDES, file) then
|
||||||
|
-- It does not make sense to test sourcemap generation for some of the test projects
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
|
||||||
|
local testProject = `{TEST_PROJECTS_DIR}/{file}`
|
||||||
|
test.case(file, function()
|
||||||
|
-- If a file starts with `bad_` but not `bad_meta_`, we should expect a failure
|
||||||
|
-- Also, sorry about this shitty regex, regex-rs does not support look-around :(
|
||||||
|
local isBadMeta = regex.new("^bad_[^m]|^bad_m[^e]|^bad_me[^t]|^bad_met[^a]")
|
||||||
|
local expect = if isBadMeta:isMatch(file) then check.should_error else check.should_not_error
|
||||||
|
|
||||||
|
expect(function()
|
||||||
|
-- We override the require which applies some patches to some builtins, mainly preventing
|
||||||
|
-- command stdout forwarding and `stdio.write` outputs to stdout in order to not fill
|
||||||
|
-- test ouput with large amounts of JSON data
|
||||||
|
local sourcemap: (string) -> boolean =
|
||||||
|
luau.load(fs.readFile("./src/generators/rojo/sourcemap.luau"), {
|
||||||
|
environment = {
|
||||||
|
require = requireWithPatches,
|
||||||
|
},
|
||||||
|
})()
|
||||||
|
|
||||||
|
return check.is_true(sourcemap(testProject))
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
|
@ -56,15 +56,13 @@ local PLATFORM_SEP = if process.os == "windows" then "\\" else "/"
|
||||||
--- ## Errors
|
--- ## Errors
|
||||||
--- * The current process lacks permissions to a file
|
--- * The current process lacks permissions to a file
|
||||||
--- * Any I/O error occurs
|
--- * Any I/O error occurs
|
||||||
return function(
|
return function(packageDirectory: string?, files: { string }, options: {
|
||||||
packageDirectory: string?,
|
writeToFile: boolean?,
|
||||||
files: { string },
|
force: boolean?,
|
||||||
writeToFile: boolean?
|
}): (boolean, string?)
|
||||||
): (boolean, string?)
|
|
||||||
packageDirectory = packageDirectory or process.cwd
|
packageDirectory = packageDirectory or process.cwd
|
||||||
local syncConfigPath =
|
local syncConfigPath = `{packageDirectory}{PLATFORM_SEP}default.project.json`
|
||||||
`{packageDirectory}{PLATFORM_SEP}default.project.json`
|
if fs.isFile(syncConfigPath) and not options.force then
|
||||||
if fs.isFile(syncConfigPath) then
|
|
||||||
return true, nil
|
return true, nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -102,9 +100,8 @@ return function(
|
||||||
|
|
||||||
-- Finally, we serialize the config to a JSON string and optionally write it
|
-- Finally, we serialize the config to a JSON string and optionally write it
|
||||||
-- to the sync config path
|
-- to the sync config path
|
||||||
local serializedConfig =
|
local serializedConfig = serde.encode("json", { tree = syncConfigTree }, true)
|
||||||
serde.encode("json", { tree = syncConfigTree }, true)
|
if options.writeToFile then
|
||||||
if writeToFile then
|
|
||||||
fs.writeFile(syncConfigPath, serializedConfig)
|
fs.writeFile(syncConfigPath, serializedConfig)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
33
src/generators/rojo/sync_config.spec.luau
Normal file
33
src/generators/rojo/sync_config.spec.luau
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
local fs = require("@lune/fs")
|
||||||
|
local serde = require("@lune/serde")
|
||||||
|
|
||||||
|
local frktest = require("../../../lune_packages/frktest")
|
||||||
|
local check = frktest.assert.check
|
||||||
|
|
||||||
|
local syncConfig = require("./sync_config")
|
||||||
|
|
||||||
|
local TEST_PROJECTS_DIRS = {
|
||||||
|
"./test-files/rojo/test-projects",
|
||||||
|
"./test-files/rojo/rojo-test/serve-tests",
|
||||||
|
}
|
||||||
|
|
||||||
|
return function(test: typeof(frktest.test))
|
||||||
|
test.suite("Generates Rojo valid sync configs", function()
|
||||||
|
for _, dir in TEST_PROJECTS_DIRS do
|
||||||
|
for _, file in fs.readDir(dir) do
|
||||||
|
local fullPath = `{dir}/{file}`
|
||||||
|
test.case(`{file}`, function()
|
||||||
|
local ok, config = syncConfig(fullPath, fs.readDir(fullPath), { writeToFile = false, force = true })
|
||||||
|
check.is_true(ok)
|
||||||
|
|
||||||
|
-- Make sure that the generated config and the real configs are similar
|
||||||
|
local generatedConfig, realConfig =
|
||||||
|
serde.decode("json", config),
|
||||||
|
serde.decode("json", fs.readFile(`{fullPath}/default.project.json`))
|
||||||
|
|
||||||
|
check.table.contains(realConfig, generatedConfig)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
|
@ -4,7 +4,7 @@ indent_type = "Spaces"
|
||||||
call_parentheses = "Always"
|
call_parentheses = "Always"
|
||||||
|
|
||||||
indent_width = 4
|
indent_width = 4
|
||||||
column_width = 80
|
# column_width = 80
|
||||||
|
|
||||||
[sort_requires]
|
[sort_requires]
|
||||||
enabled = true
|
enabled = true
|
||||||
|
|
1
test-files/rojo
Submodule
1
test-files/rojo
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit b7d3394464e0ffb350b9c8481399cc5845c10f07
|
Loading…
Reference in a new issue