diff --git a/.gitignore b/.gitignore index c7c40ed..10f5886 100644 --- a/.gitignore +++ b/.gitignore @@ -7,5 +7,5 @@ globaltypes.d.lua Packages DevPackages -sourcemap.json +*sourcemap.json settings.json \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 071f036..42c27d0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -146,5 +146,35 @@ You need to set the `FFlagEnableLoadModule` value to `true`. Be sure to restart | Method | Instructions | | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| CLI (recommended) | `lune run runTests` | +| CLI (recommended) | `lune run test` | | Roblox Studio | Open the test place file `StateMachine-Test.rbxl` [built in the above step](#build-the-project) in Roblox Studio and run the place (server only). The output widget will show the test results. | + + +### Continuous Integration (CI) + +CI checks are set up to run on pull requests. These checks must pass before merging, including: + +1. `lint` with selene +1. `format check` with StyLua +1. `analyze` with luau-lsp +1. `test` with jest running in Lune + +#### Running CI Locally + +To run the same CI checks locally that would run on GitHub, a number of Lune scripts are provided. + +From the project directory, you can run the following: + +> lune run ci + +This will run all the same checks that would run on GitHub. + +Alternatively, you can run individual steps yourself: + +> lune run lint + +> lune run formatCheck + +> lune run analyze + +> lune run test \ No newline at end of file diff --git a/lune/Context/Debug.lua b/lune/Context/Debug.lua index 7cb4315..4268a0a 100644 --- a/lune/Context/Debug.lua +++ b/lune/Context/Debug.lua @@ -5,7 +5,7 @@ On Roblox, it's locked behind a feature flag FFlagDebugLoadModule, which is not enabled by default. In Lune, it doesn't exist at all, so this module creates the interface for it but is not implemented. The _loader function needs to be overridden to be usable. - In this project, the `lune/runTests.lua` script implements and sets the _loader function. + In this project, the `lune/test.lua` script implements and sets the _loader function. --]] local Debug diff --git a/lune/Utils/Runtime.lua b/lune/Utils/Runtime.lua index ab5f1db..21b36f1 100644 --- a/lune/Utils/Runtime.lua +++ b/lune/Utils/Runtime.lua @@ -2,7 +2,7 @@ --[[ Provides a connection to a loop that runs every frame. This is used - in the custom Heartbeat implementation in lune/runTests.lua + in the custom Heartbeat implementation in lune/test.lua --]] local task = require("@lune/task") diff --git a/lune/analyze.lua b/lune/analyze.lua new file mode 100644 index 0000000..ae37fe4 --- /dev/null +++ b/lune/analyze.lua @@ -0,0 +1,93 @@ +--!strict + +--[[ + Analyzes all the same paths that get analyzed in the CI pipeline. + + Since the parent folder is named `lune`, the `lune` cli will automatically look in this directory for scripts to run. + + Usage (from project directory): + lune run analyze +--]] + +local process = require("@lune/process") +local stdio = require("@lune/stdio") +local task = require("@lune/task") + +local ANALYZE_PATHS: { { path: string, project: string?, sourceMap: string? } } = { + { + path = "src/StateMachine", + project = "default.project.json", + sourceMap = "stateMachineSourcemap.json", + }, + { + path = "src/TestService", + project = "test.project.json", + sourceMap = "testSourcemap.json", + }, + { + path = "lune", + project = nil, + sourceMap = nil, + }, +} + +local NUM_EXPECTED_TASKS = #ANALYZE_PATHS + +local numErrors = 0 +local numTasksCompleted = 0 +local mainThread = coroutine.running() + +local function buildSourceMap(projectFilePath: string, sourceMapFilePath: string) + local proc = process.spawn("./scripts/sourcemap.sh", { projectFilePath, sourceMapFilePath }) + print(proc.stdout) + assert(proc.ok, proc.stderr) +end + +local function analyzePath(path: string, sourceMap: string?) + local proc + if sourceMap then + proc = process.spawn("./scripts/analyze.sh", { sourceMap, path }) + else + proc = process.spawn("./scripts/analyze.sh", { path }) + end + + print(proc.stdout) + assert(proc.ok, proc.stderr) +end + +local function analyzeAllPaths() + print(`Analyzing {NUM_EXPECTED_TASKS} paths:`) + for _, path in ipairs(ANALYZE_PATHS) do + task.spawn(function() + local success, errorMessage: string? = pcall(function() + if path.project and path.sourceMap then + buildSourceMap(path.project, path.sourceMap) + end + analyzePath(path.path, path.sourceMap) + end) + + if not success then + stdio.ewrite(errorMessage :: string) + numErrors += 1 + end + + numTasksCompleted += 1 + if numTasksCompleted == NUM_EXPECTED_TASKS then + coroutine.resume(mainThread) + end + end) + end + + if numTasksCompleted < NUM_EXPECTED_TASKS then + coroutine.yield() + end + + if numErrors > 0 then + stdio.ewrite(`{numErrors} of {NUM_EXPECTED_TASKS} paths FAILED analysis\n`) + process.exit(1) + end + + stdio.write(`All ({NUM_EXPECTED_TASKS}) paths analyzed successfully\n`) +end + +analyzeAllPaths() diff --git a/lune/ci.lua b/lune/ci.lua new file mode 100644 index 0000000..c090bb9 --- /dev/null +++ b/lune/ci.lua @@ -0,0 +1,62 @@ +--!strict + +--[[ + Runs all the same checks that CI steps run, including analysis, format check, linting, and testing. + + Since the parent folder is named `lune`, the `lune` cli will automatically look in this directory for scripts to run. + + Usage (from project directory): + lune run ci +--]] + +local process = require("@lune/process") +local stdio = require("@lune/stdio") +local task = require("@lune/task") + +local LUNE_COMMANDS = { + "lint", + "formatCheck", + "analyze", + "test", +} + +local NUM_EXPECTED_TASKS = #LUNE_COMMANDS + +local numErrors = 0 +local numTasksCompleted = 0 +local mainThread = coroutine.running() + +local function runLuneCommand(command: string) + local home = process.env.HOME + local proc = process.spawn(`{home}/.aftman/bin/lune`, { "run", command }) + print(proc.stdout) + if not proc.ok then + stdio.ewrite(proc.stderr) + numErrors += 1 + end + + numTasksCompleted += 1 + if numTasksCompleted == NUM_EXPECTED_TASKS then + coroutine.resume(mainThread) + end +end + +local function runAllLuneCommands() + print(`Fixing format for {NUM_EXPECTED_TASKS} paths:`) + for _, path in ipairs(LUNE_COMMANDS) do + task.spawn(runLuneCommand, path) + end + + if numTasksCompleted < NUM_EXPECTED_TASKS then + coroutine.yield() + end + + if numErrors > 0 then + stdio.ewrite(`{numErrors} of {NUM_EXPECTED_TASKS} commands FAILED\n`) + process.exit(1) + end + + stdio.write(`All ({NUM_EXPECTED_TASKS}) commands succeeded\n`) +end + +runAllLuneCommands() diff --git a/lune/formatCheck.lua b/lune/formatCheck.lua new file mode 100644 index 0000000..975a559 --- /dev/null +++ b/lune/formatCheck.lua @@ -0,0 +1,62 @@ +--!strict + +--[[ + Checks the format for all the same paths that get checked in the CI pipeline. + This is just a check that prints problems to the output. + To fix the format, run `lune run formatFix` instead. + + Since the parent folder is named `lune`, the `lune` cli will automatically look in this directory for scripts to run. + + Usage (from project directory): + lune run formatCheck +--]] + +local process = require("@lune/process") +local stdio = require("@lune/stdio") +local task = require("@lune/task") + +local FORMAT_PATHS = { + "src/StateMachine", + "src/TestService", + "lune", +} + +local NUM_EXPECTED_TASKS = #FORMAT_PATHS + +local numErrors = 0 +local numTasksCompleted = 0 +local mainThread = coroutine.running() + +local function checkFormatForPath(path: string) + local proc = process.spawn("./scripts/formatCheck.sh", { path }) + print(proc.stdout) + if not proc.ok then + stdio.ewrite(proc.stderr) + numErrors += 1 + end + + numTasksCompleted += 1 + if numTasksCompleted == NUM_EXPECTED_TASKS then + coroutine.resume(mainThread) + end +end + +local function formatCheckAllPaths() + print(`Checking format for {NUM_EXPECTED_TASKS} paths:`) + for _, path in ipairs(FORMAT_PATHS) do + task.spawn(checkFormatForPath, path) + end + + if numTasksCompleted < NUM_EXPECTED_TASKS then + coroutine.yield() + end + + if numErrors > 0 then + stdio.ewrite(`{numErrors} of {NUM_EXPECTED_TASKS} paths FAILED formatting checks\n`) + process.exit(1) + end + + stdio.write(`All ({NUM_EXPECTED_TASKS}) paths passed format checks successfully\n`) +end + +formatCheckAllPaths() diff --git a/lune/formatFix.lua b/lune/formatFix.lua new file mode 100644 index 0000000..d326636 --- /dev/null +++ b/lune/formatFix.lua @@ -0,0 +1,63 @@ +--!strict + +--[[ + Fixes the format for all the same paths that get fixed in the CI pipeline. + This changes the files in place. + To just check the format, run `lune run formatCheck` instead. + + Since the parent folder is named `lune`, the `lune` cli will automatically look in this directory for scripts to run. + + Usage (from project directory): + lune run formatFix +--]] + +local process = require("@lune/process") +local stdio = require("@lune/stdio") +local task = require("@lune/task") + +local FORMAT_PATHS = { + "src/StateMachine", + "src/TestService", + "lune", +} + +local NUM_EXPECTED_TASKS = #FORMAT_PATHS + +local numErrors = 0 +local numTasksCompleted = 0 +local mainThread = coroutine.running() + +local function fixFormatForPath(path: string) + local home = process.env.HOME + local proc = process.spawn(`{home}/.aftman/bin/stylua`, { path }) + print(proc.stdout) + if not proc.ok then + stdio.ewrite(proc.stderr) + numErrors += 1 + end + + numTasksCompleted += 1 + if numTasksCompleted == NUM_EXPECTED_TASKS then + coroutine.resume(mainThread) + end +end + +local function formatFixAllPaths() + print(`Fixing format for {NUM_EXPECTED_TASKS} paths:`) + for _, path in ipairs(FORMAT_PATHS) do + task.spawn(fixFormatForPath, path) + end + + if numTasksCompleted < NUM_EXPECTED_TASKS then + coroutine.yield() + end + + if numErrors > 0 then + stdio.ewrite(`{numErrors} of {NUM_EXPECTED_TASKS} paths FAILED to get format fixed\n`) + process.exit(1) + end + + stdio.write(`All ({NUM_EXPECTED_TASKS}) paths have fixed formats\n`) +end + +formatFixAllPaths() diff --git a/lune/lint.lua b/lune/lint.lua new file mode 100644 index 0000000..5879425 --- /dev/null +++ b/lune/lint.lua @@ -0,0 +1,60 @@ +--!strict + +--[[ + Lints all the same paths that get linted in the CI pipeline. + + Since the parent folder is named `lune`, the `lune` cli will automatically look in this directory for scripts to run. + + Usage (from project directory): + lune run lint +--]] + +local process = require("@lune/process") +local stdio = require("@lune/stdio") +local task = require("@lune/task") + +local LINT_PATHS = { + "src/StateMachine", + "src/TestService", + "lune", +} + +local NUM_EXPECTED_TASKS = #LINT_PATHS + +local numErrors = 0 +local numTasksCompleted = 0 +local mainThread = coroutine.running() + +local function lintPath(path: string) + local proc = process.spawn("./scripts/lint.sh", { path }) + print(proc.stdout) + if not proc.ok then + stdio.ewrite(proc.stderr) + numErrors += 1 + end + + numTasksCompleted += 1 + if numTasksCompleted == NUM_EXPECTED_TASKS then + coroutine.resume(mainThread) + end +end + +local function lintAllPaths() + print(`Linting {NUM_EXPECTED_TASKS} paths:`) + for _, path in ipairs(LINT_PATHS) do + task.spawn(lintPath, path) + end + + if numTasksCompleted < NUM_EXPECTED_TASKS then + coroutine.yield() + end + + if numErrors > 0 then + stdio.ewrite(`{numErrors} of {NUM_EXPECTED_TASKS} paths FAILED linting\n`) + process.exit(1) + end + + stdio.write(`All ({NUM_EXPECTED_TASKS}) paths linted successfully\n`) +end + +lintAllPaths() diff --git a/lune/runTests.lua b/lune/test.lua similarity index 98% rename from lune/runTests.lua rename to lune/test.lua index c72eca4..4e92282 100644 --- a/lune/runTests.lua +++ b/lune/test.lua @@ -7,7 +7,7 @@ Since the parent folder is named `lune`, the `lune` cli will automatically look in this directory for scripts to run. Usage (from project directory): - lune run runTests [project.json] + lune run test [project.json] --]] local fs = require("@lune/fs") @@ -22,7 +22,7 @@ local DateTime = require("Context/DateTime") local Debug = require("Context/Debug") local Runtime = require("Utils/Runtime") --- DEPENDENTS: [runTests.lua, Jest] +-- DEPENDENTS: [test.lua, Jest] local ReducedInstance = require("Utils/ReducedInstance") type RojoProject = { @@ -120,7 +120,7 @@ local gameWithContext = setmetatable({ end, }, { __index = game }) --- DEPENDENTS: [runTests.lua, Jest] +-- DEPENDENTS: [test.lua, Jest] local function loadScript(script: roblox.Instance): (((...any) -> ...any)?, string?) script = ReducedInstance.once(script) if not script:IsA("LuaSourceContainer") then diff --git a/scripts/analyze.sh b/scripts/analyze.sh index 01108e5..859d6dc 100755 --- a/scripts/analyze.sh +++ b/scripts/analyze.sh @@ -19,6 +19,7 @@ if [ -n "$2" ]; then --sourcemap "$1" \ --ignore "*Packages/**" \ "$2" + echo "Analysis of $2 complete!" else echo "Beginning analysis on $1 with settings from $SETTINGS_FILE and global types from $TYPES_FILE..." $HOME/.aftman/bin/luau-lsp analyze \ @@ -26,6 +27,5 @@ else --definitions=$TYPES_FILE \ --ignore "*Packages/**" \ "$1" + echo "Analysis of $1 complete!" fi - -echo "Analysis complete!" \ No newline at end of file diff --git a/scripts/formatCheck.sh b/scripts/formatCheck.sh index b5aa61f..87f833e 100755 --- a/scripts/formatCheck.sh +++ b/scripts/formatCheck.sh @@ -6,4 +6,4 @@ echo "Beginning format check on $1..." $HOME/.aftman/bin/stylua --check $1 -echo "Format check complete!" \ No newline at end of file +echo "Format check on $1 complete!" \ No newline at end of file diff --git a/scripts/lint.sh b/scripts/lint.sh index 0a6cc0f..7412914 100755 --- a/scripts/lint.sh +++ b/scripts/lint.sh @@ -6,4 +6,4 @@ echo "Beginning linting on $1..." $HOME/.aftman/bin/selene $1 -echo "Linting complete!" \ No newline at end of file +echo "Linting $1 complete!" \ No newline at end of file diff --git a/scripts/sourcemap.sh b/scripts/sourcemap.sh index 2b6025e..853b0c6 100755 --- a/scripts/sourcemap.sh +++ b/scripts/sourcemap.sh @@ -6,4 +6,4 @@ echo "Creating sourcemap from $1 to $2..." $HOME/.aftman/bin/rojo sourcemap $1 --output $2 -echo "Sourcemap created!" \ No newline at end of file +echo "Sourcemap $2 created!" \ No newline at end of file diff --git a/scripts/test.sh b/scripts/test.sh index decbf9e..f7d738a 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -4,6 +4,6 @@ set -e echo "Starting test runner..." -$HOME/.aftman/bin/lune run runTests +$HOME/.aftman/bin/lune run test echo "Test runner complete!" \ No newline at end of file