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
|
||||
}
|
||||
}
|
|
@ -5,15 +5,13 @@ local function enter(fn: (args: { string }) -> number?): never
|
|||
local startTime = os.clock()
|
||||
local exitCode = fn(table.clone(process.args))
|
||||
|
||||
stdio.write(
|
||||
`done in {stdio.style("dim")}{string.format("%.2fs", os.clock() - startTime)}{stdio.style("reset")}!\n`
|
||||
)
|
||||
stdio.write(`done in {stdio.style("dim")}{string.format("%.2fs", os.clock() - startTime)}{stdio.style("reset")}!\n`)
|
||||
|
||||
return process.exit(exitCode)
|
||||
end
|
||||
|
||||
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)
|
||||
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>"]
|
||||
repository = "https://github.com/pesde-pkg/scripts"
|
||||
license = "MIT"
|
||||
includes = ["src/**/*.luau", "README.md", "LICENSE.md"]
|
||||
includes = ["src/**/*.luau", "pesde.toml", "README.md", "LICENSE.md"]
|
||||
|
||||
[target]
|
||||
environment = "lune"
|
||||
|
@ -13,3 +13,6 @@ lib = "src/init.luau"
|
|||
|
||||
[indices]
|
||||
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,
|
||||
|
||||
["init.lua"] = function()
|
||||
return stdio.write(
|
||||
serde.encode("json", { filePaths = { "init.lua" } }, false)
|
||||
)
|
||||
return stdio.write(serde.encode("json", { filePaths = { "init.lua" } }, false))
|
||||
end,
|
||||
["init.luau"] = function()
|
||||
return stdio.write(
|
||||
serde.encode("json", { filePaths = { "init.luau" } }, false)
|
||||
)
|
||||
return stdio.write(serde.encode("json", { filePaths = { "init.luau" } }, false))
|
||||
end,
|
||||
}
|
||||
|
||||
|
@ -32,6 +28,7 @@ local PATH_ACTION_MAP: { [string]: (dir: string) -> number? } = {
|
|||
|
||||
--- ## Errors
|
||||
--- * The current process lacks permissions to a file
|
||||
--- * Failure to spawn `rojo` command
|
||||
--- * Any I/O error occurs
|
||||
return function(packageDirectory: string?): boolean
|
||||
packageDirectory = packageDirectory or process.cwd
|
||||
|
|
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
|
||||
--- * The current process lacks permissions to a file
|
||||
--- * Any I/O error occurs
|
||||
return function(
|
||||
packageDirectory: string?,
|
||||
files: { string },
|
||||
writeToFile: boolean?
|
||||
): (boolean, string?)
|
||||
return function(packageDirectory: string?, files: { string }, options: {
|
||||
writeToFile: boolean?,
|
||||
force: boolean?,
|
||||
}): (boolean, string?)
|
||||
packageDirectory = packageDirectory or process.cwd
|
||||
local syncConfigPath =
|
||||
`{packageDirectory}{PLATFORM_SEP}default.project.json`
|
||||
if fs.isFile(syncConfigPath) then
|
||||
local syncConfigPath = `{packageDirectory}{PLATFORM_SEP}default.project.json`
|
||||
if fs.isFile(syncConfigPath) and not options.force then
|
||||
return true, nil
|
||||
end
|
||||
|
||||
|
@ -102,9 +100,8 @@ return function(
|
|||
|
||||
-- Finally, we serialize the config to a JSON string and optionally write it
|
||||
-- to the sync config path
|
||||
local serializedConfig =
|
||||
serde.encode("json", { tree = syncConfigTree }, true)
|
||||
if writeToFile then
|
||||
local serializedConfig = serde.encode("json", { tree = syncConfigTree }, true)
|
||||
if options.writeToFile then
|
||||
fs.writeFile(syncConfigPath, serializedConfig)
|
||||
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"
|
||||
|
||||
indent_width = 4
|
||||
column_width = 80
|
||||
# column_width = 80
|
||||
|
||||
[sort_requires]
|
||||
enabled = true
|
||||
|
|
1
test-files/rojo
Submodule
1
test-files/rojo
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit b7d3394464e0ffb350b9c8481399cc5845c10f07
|
Loading…
Reference in a new issue