diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0986719..2167dc3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,22 +1,70 @@ name: CI -on: [push, pull_request, workflow_dispatch] +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +permissions: + contents: read env: JULIA_VERSION: '1.12.6' jobs: - cpu: - runs-on: macos-15 + package: + runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: julia-actions/setup-julia@v3 with: {version: '${{ env.JULIA_VERSION }}'} - uses: julia-actions/cache@v3 - name: Package tests - run: julia --project=. --startup-file=no -e 'using Pkg; Pkg.instantiate(); Pkg.test()' + run: julia --project=. --startup-file=no -e 'using Pkg; Pkg.instantiate(); Pkg.test(; test_args=["--jobs=2", "--verbose"])' + + scientific: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: julia-actions/setup-julia@v3 + with: {version: '${{ env.JULIA_VERSION }}'} + - uses: julia-actions/cache@v3 - name: Scientific witnesses run: julia --project=test/scientific_witnesses --startup-file=no -e 'using Pkg; Pkg.develop(path="."); Pkg.instantiate(); include("test/scientific_witnesses/runtests.jl")' + + macos-smoke: + runs-on: macos-15 + steps: + - uses: actions/checkout@v6 + - uses: julia-actions/setup-julia@v3 + with: {version: '${{ env.JULIA_VERSION }}'} + - uses: julia-actions/cache@v3 + - name: Public API and KernelAbstractions smoke tests + run: julia --project=. --startup-file=no -e 'using Pkg; Pkg.instantiate(); Pkg.test(; test_args=["--jobs=1", "test_public_api", "test_direct_pointwise_stage"])' + + macos-package: + if: github.event_name != 'pull_request' + runs-on: macos-15 + steps: + - uses: actions/checkout@v6 + - uses: julia-actions/setup-julia@v3 + with: {version: '${{ env.JULIA_VERSION }}'} + - uses: julia-actions/cache@v3 + - name: Complete macOS package tests + run: julia --project=. --startup-file=no -e 'using Pkg; Pkg.instantiate(); Pkg.test(; test_args=["--jobs=2"])' + + docs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: julia-actions/setup-julia@v3 + with: {version: '${{ env.JULIA_VERSION }}'} + - uses: julia-actions/cache@v3 - name: Documentation run: julia --project=docs --startup-file=no -e 'using Pkg; Pkg.develop(path="."); Pkg.instantiate(); include("docs/make.jl")' diff --git a/test/Project.toml b/test/Project.toml index bef170e..5ce872a 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -3,6 +3,7 @@ Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" ExplicitImports = "7d51a73a-1435-4ff3-83d9-f097790105c7" KernelAbstractions = "63c18a36-062a-441e-b654-da1e3ab1ce7c" LocalMath = "82808884-8acc-4a5c-a056-be440a0fbea1" +ParallelTestRunner = "d3525ed8-44d0-4b2c-a655-542cee43accc" StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" @@ -12,6 +13,7 @@ TOML = "fa267f1f-6049-4f14-aa54-33bafae1ed76" Aqua = "0.8" ExplicitImports = "1" KernelAbstractions = "0.9.42" +ParallelTestRunner = "2.8" StaticArrays = "1" Statistics = "1" Test = "1" diff --git a/test/runtests.jl b/test/runtests.jl index 5bbc3eb..06bb90a 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,59 +1,7 @@ -using Aqua -using ExplicitImports +using ParallelTestRunner using Test import LocalMath -@testset "LocalMath package quality" begin - Aqua.test_all(LocalMath; ambiguities = false) - # These qualified non-public accesses implement the reviewed central - # admission, compiler-identity, storage-alias, and backend-validation - # boundaries. Keep the allowlist exact so new private reliance still fails. - qualified_internal_boundary = ( - Symbol("@adapt_structure"), - Symbol("@atomic"), - Symbol("@nospecializeinfer"), - :Argument, - :CodeInfo, - :CodeInstance, - :Compiler, - :Const, - :LLVMPtr, - :MethodInstance, - :PkgId, - :SSAValue, - :SlotNumber, - :Typeof, - :apply_type, - :checked_mul, - :code_typed_by_type, - :datatype_alignment, - :device, - :functional, - :get_world_counter, - :ifelse, - :inferencebarrier, - :invoke_in_world, - :loaded_modules, - :mightalias, - :return_type, - :throw_boundserror, - :throw_inexacterror, - :typename, - :typesof, - :uncompressed_ast, - :unwrap_unionall, - ) - ExplicitImports.test_explicit_imports( - LocalMath; - all_qualified_accesses_are_public = - (; ignore = qualified_internal_boundary), - ) - @test isempty(Test.detect_ambiguities( - LocalMath, Base; recursive = true - )) -end - -include("support.jl") const LOCALMATH_INCLUDED_TESTS = ( "test_public_api.jl", "test_spatial_model.jl", @@ -91,10 +39,91 @@ const LOCALMATH_INCLUDED_TESTS = ( "test_semantic_oracles.jl", ) -@testset "LocalMath test runner inventory" begin - discovered = sort(filter(name -> - startswith(name, "test_") && endswith(name, ".jl"), readdir(@__DIR__))) - @test sort(collect(LOCALMATH_INCLUDED_TESTS)) == discovered +const LOCALMATH_TEST_SUITE = Dict{String, Expr}( + "package_quality" => quote + using Aqua + using ExplicitImports + + @testset "LocalMath package quality" begin + Aqua.test_all(LocalMath; ambiguities = false) + # These qualified non-public accesses implement the reviewed + # central admission, compiler-identity, storage-alias, and + # backend-validation boundaries. Keep the allowlist exact so new + # private reliance still fails. + qualified_internal_boundary = ( + Symbol("@adapt_structure"), + Symbol("@atomic"), + Symbol("@nospecializeinfer"), + :Argument, + :CodeInfo, + :CodeInstance, + :Compiler, + :Const, + :LLVMPtr, + :MethodInstance, + :PkgId, + :SSAValue, + :SlotNumber, + :Typeof, + :apply_type, + :checked_mul, + :code_typed_by_type, + :datatype_alignment, + :device, + :functional, + :get_world_counter, + :ifelse, + :inferencebarrier, + :invoke_in_world, + :loaded_modules, + :mightalias, + :return_type, + :throw_boundserror, + :throw_inexacterror, + :typename, + :typesof, + :uncompressed_ast, + :unwrap_unionall, + ) + ExplicitImports.test_explicit_imports( + LocalMath; + all_qualified_accesses_are_public = + (; ignore = qualified_internal_boundary), + ) + @test isempty(Test.detect_ambiguities( + LocalMath, Base; recursive = true + )) + end + end, + "test_runner_inventory" => quote + @testset "LocalMath test runner inventory" begin + discovered = sort(filter( + name -> startswith(name, "test_") && endswith(name, ".jl"), + readdir(@__DIR__), + )) + @test sort(collect($LOCALMATH_INCLUDED_TESTS)) == discovered + end + end, +) + +for test_file in LOCALMATH_INCLUDED_TESTS + test_name = first(splitext(test_file)) + LOCALMATH_TEST_SUITE[test_name] = quote + include($(joinpath(@__DIR__, "support.jl"))) + include($(joinpath(@__DIR__, test_file))) + end +end + +const LOCALMATH_TEST_INITIALIZATION = quote + using Test + import KernelAbstractions + import LocalMath end -foreach(include, LOCALMATH_INCLUDED_TESTS) +ParallelTestRunner.runtests( + LocalMath, + ARGS; + testsuite = LOCALMATH_TEST_SUITE, + init_code = LOCALMATH_TEST_INITIALIZATION, + serial = ["package_quality", "test_runner_inventory"], +) diff --git a/test/support.jl b/test/support.jl index 330649b..b8c0f28 100644 --- a/test/support.jl +++ b/test/support.jl @@ -9,3 +9,34 @@ function _test_stage_admission(bound; backend, index::Integer = 1) 1 <= index <= length(entries) || throw(BoundsError(entries, index)) return entries[index].admission end + +struct ReceiptTestNode end +struct ReceiptTestEvaluator{T} + value::T +end + +@inline (evaluator::ReceiptTestEvaluator)(item::Int32, reads, parameters) = + (value = LM.UniqueValue(evaluator.value + item),) + +function _receipt_test_preparation(value::Int32; + dependency_arity::Int = 0, lease_capacity::Int = 1, + evaluator = ReceiptTestEvaluator(value)) + space = LM.Space(ReceiptTestNode, 2) + output = LM.Field(space, Int32) + relation = LM.IdentityRelation(space) + publication = LM.Publication((LM.FieldPublication( + output, relation, LM.PublicationValue(:value)),), + LM.Unique(Int32)) + stage = LM.Stage(space, NamedTuple(), (publication,), + LM.Evaluator(evaluator), LM.Control(), + LM.SourceOrigin(:execution_receipt_test, 1)) + storage = fill(Int32(-1), 2) + bound = LM._bind_law(LM.LocalLaw(stage), + LM._StructuralBinding( + (LM._field_storage_binding(output, storage),), + (LM._relation_storage_binding(relation),))) + backend = LM.KernelAbstractions.get_backend(storage) + prepared = LM.prepare(LM.plan(bound; backend); + dependency_arity, lease_capacity) + return prepared, storage +end diff --git a/test/test_execution_receipts.jl b/test/test_execution_receipts.jl index e176562..d7f8a47 100644 --- a/test/test_execution_receipts.jl +++ b/test/test_execution_receipts.jl @@ -2,12 +2,6 @@ using Test import LocalMath const LWER = LocalMath -struct ReceiptTestNode end -struct ReceiptTestEvaluator{T} - value::T -end -@inline (evaluator::ReceiptTestEvaluator)(item::Int32, reads, parameters) = - (value = LWER.UniqueValue(evaluator.value + item),) struct ReceiptProviderFailureEvaluator end @inline function (::ReceiptProviderFailureEvaluator)( item::Int32, reads, parameters) @@ -15,29 +9,6 @@ struct ReceiptProviderFailureEvaluator end error("intentional provider execution failure") end -function _receipt_test_preparation(value::Int32; - dependency_arity::Int = 0, lease_capacity::Int = 1, - evaluator = ReceiptTestEvaluator(value)) - space = LWER.Space(ReceiptTestNode, 2) - output = LWER.Field(space, Int32) - relation = LWER.IdentityRelation(space) - publication = LWER.Publication((LWER.FieldPublication( - output, relation, LWER.PublicationValue(:value)),), - LWER.Unique(Int32)) - stage = LWER.Stage(space, NamedTuple(), (publication,), - LWER.Evaluator(evaluator), LWER.Control(), - LWER.SourceOrigin(:execution_receipt_test, 1)) - storage = fill(Int32(-1), 2) - bound = LWER._bind_law(LWER.LocalLaw(stage), - LWER._StructuralBinding( - (LWER._field_storage_binding(output, storage),), - (LWER._relation_storage_binding(relation),))) - backend = LWER.KernelAbstractions.get_backend(storage) - prepared = LWER.prepare(LWER.plan(bound; backend); - dependency_arity, lease_capacity) - return prepared, storage -end - function _receipt_test_conflict_preparation(; dependency_arity::Int = 0) source = LWER.Space(ReceiptTestNode, 2) destination = LWER.Space(ReceiptTestNode, 1)