diff --git a/.bazelrc b/.bazelrc index f6d34b6bb..7b0a188da 100644 --- a/.bazelrc +++ b/.bazelrc @@ -111,6 +111,12 @@ build: --incompatible_override_toolchain_transition coverage --build_tag_filters "coverage-compatible" --test_tag_filters "coverage-compatible" --test_output=all +# To update these lines, execute +# `bazel run @contrib_rules_bazel_integration_test//tools:update_deleted_packages` +build --deleted_packages=examples,examples/arm,examples/cat_hs,examples/cat_hs/exec/cat_hs,examples/cat_hs/lib/args,examples/cat_hs/lib/cat,examples/primitive,examples/rts,examples/transformers,examples/vector,tests/c2hs/repo,tests/haskell_module/repl/haskell_module_repl_cross_library_deps_test/package-a,tests/haskell_module/repl/haskell_module_repl_cross_library_deps_test/package-b,tests/haskell_module/repl/haskell_module_repl_test,tests/library-external-workspace/repo,tests/repl-targets/hs_bin_repl_test,tests/repl-targets/hs_lib_repl_test,tests/stack-snapshot-deps/hs_override_stack_test,tutorial,tutorial/lib,tutorial/main,tutorial/tools/build_rules +query --deleted_packages=examples,examples/arm,examples/cat_hs,examples/cat_hs/exec/cat_hs,examples/cat_hs/lib/args,examples/cat_hs/lib/cat,examples/primitive,examples/rts,examples/transformers,examples/vector,tests/c2hs/repo,tests/haskell_module/repl/haskell_module_repl_cross_library_deps_test/package-a,tests/haskell_module/repl/haskell_module_repl_cross_library_deps_test/package-b,tests/haskell_module/repl/haskell_module_repl_test,tests/library-external-workspace/repo,tests/repl-targets/hs_bin_repl_test,tests/repl-targets/hs_lib_repl_test,tests/stack-snapshot-deps/hs_override_stack_test,tutorial,tutorial/lib,tutorial/main,tutorial/tools/build_rules + + # User Configuration # ------------------ try-import %workspace%/.bazelrc.local diff --git a/BUILD.bazel b/BUILD.bazel old mode 100644 new mode 100755 index c74ea3d69..8b56d8563 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -19,6 +19,7 @@ filegroup( name = "distribution", testonly = True, srcs = [ + ".bazelrc", "BUILD.bazel", "WORKSPACE", "constants.bzl", diff --git a/WORKSPACE b/WORKSPACE old mode 100644 new mode 100755 index d9f6501db..7cedeb224 --- a/WORKSPACE +++ b/WORKSPACE @@ -532,16 +532,8 @@ register_toolchains( # For buildifier # starting from 0.29, rules_go requires bazel >= 4.2.0 -# rules_go dependency used only for rules_haskell developers for integration testing -# and it doesn't needed for downstream users of rules_haskell -# this patch is needed in order to use bazel integration testing mechanism from rules_go: -# 1. it fixes the issue with go_bazel_test on Windows: https://github.com/bazelbuild/rules_go/issues/3034 -# fix will be available in rules_go starting from 0.30.0 -# 2. it makes outputUserRoot variable public and available for commands running bazel functions redefined in rules_haskell http_archive( name = "io_bazel_rules_go", - patch_args = ["-p1"], - patches = ["//:rules_go_integration_testing.patch"], sha256 = "8e968b5fcea1d2d64071872b12737bbb5514524ee5f0a4f54f5920266c261acb", urls = [ "https://mirror.bazel.build/github.com/bazelbuild/rules_go/releases/download/v0.28.0/rules_go-v0.28.0.zip", @@ -578,6 +570,23 @@ load("@com_github_bazelbuild_buildtools//buildifier:deps.bzl", "buildifier_depen buildifier_dependencies() +http_archive( + name = "contrib_rules_bazel_integration_test", + sha256 = "f80c4052df80e9099ed0f2f27ef4084604333566a7b028f524ceae6e5569b429", + strip_prefix = "rules_bazel_integration_test-7ee995a20bbaa2f6540103c63ff4891166133c2f", + urls = [ + "https://github.com/bazel-contrib/rules_bazel_integration_test/archive/7ee995a20bbaa2f6540103c63ff4891166133c2f.zip", + ], +) + +load("@contrib_rules_bazel_integration_test//bazel_integration_test:deps.bzl", "bazel_integration_test_rules_dependencies") + +bazel_integration_test_rules_dependencies() + +load("@cgrindel_bazel_starlib//:deps.bzl", "bazel_starlib_dependencies") + +bazel_starlib_dependencies() + # For profiling # Required to make use of `bazel build --profile`. @@ -590,12 +599,9 @@ bind( # For persistent worker (tools/worker) load("//tools:repositories.bzl", "rules_haskell_worker_dependencies") -load("//tools:repositories.bzl", "bazel_binaries_for_integration_testing") rules_haskell_worker_dependencies() -bazel_binaries_for_integration_testing() - # Stack snapshot repository for testing non standard toolchains # The toolchain_libraries rule provide a default value for the toolchain_libraries # variable, so we can load it even if we are not on linux. @@ -617,6 +623,13 @@ stack_snapshot( toolchain_libraries = toolchain_libraries, ) if is_linux else None +load( + "//tests/integration_testing:dependencies.bzl", + "integration_testing_bazel_binaries", +) + +integration_testing_bazel_binaries() + local_repository( name = "tutorial", path = "tutorial", diff --git a/bazel_versions.bzl b/bazel_versions.bzl new file mode 100644 index 000000000..4b6ee2a73 --- /dev/null +++ b/bazel_versions.bzl @@ -0,0 +1,7 @@ +SUPPORTED_BAZEL_VERSIONS = [ + "4.1.0", +] + +SUPPORTED_NIXPKGS_BAZEL_PACKAGES = [ + "bazel_4", +] diff --git a/rules_go_integration_testing.patch b/rules_go_integration_testing.patch deleted file mode 100644 index 4a8b69af8..000000000 --- a/rules_go_integration_testing.patch +++ /dev/null @@ -1,47 +0,0 @@ -diff --git a/go/tools/bazel_testing/bazel_testing.go b/go/tools/bazel_testing/bazel_testing.go -index 4d1ea2f8..9d8ea3ca 100644 ---- a/go/tools/bazel_testing/bazel_testing.go -+++ b/go/tools/bazel_testing/bazel_testing.go -@@ -86,11 +86,11 @@ type Args struct { - // instead of running tests. - const debug = false - --// outputUserRoot is set to the directory where Bazel should put its internal files. -+// OutputUserRoot is set to the directory where Bazel should put its internal files. - // Since Bazel 2.0.0, this needs to be set explicitly to avoid it defaulting to a - // deeply nested directory within the test, which runs into Windows path length limits. - // We try to detect the original value in setupWorkspace and set it to that. --var outputUserRoot string -+var OutputUserRoot string - - // TestMain should be called by tests using this framework from a function named - // "TestMain". For example: -@@ -164,8 +164,8 @@ func TestMain(m *testing.M, args Args) { - // hide that this code is executing inside a bazel test. - func BazelCmd(args ...string) *exec.Cmd { - cmd := exec.Command("bazel") -- if outputUserRoot != "" { -- cmd.Args = append(cmd.Args, "--output_user_root="+outputUserRoot) -+ if OutputUserRoot != "" { -+ cmd.Args = append(cmd.Args, "--output_user_root="+OutputUserRoot) - } - cmd.Args = append(cmd.Args, args...) - for _, e := range os.Environ() { -@@ -263,7 +263,7 @@ func setupWorkspace(args Args, files []string) (dir string, cleanup func() error - tmpDir = filepath.Clean(tmpDir) - if i := strings.Index(tmpDir, string(os.PathSeparator)+"execroot"+string(os.PathSeparator)); i >= 0 { - outBaseDir = tmpDir[:i] -- outputUserRoot = filepath.Dir(outBaseDir) -+ OutputUserRoot = filepath.Dir(outBaseDir) - cacheDir = filepath.Join(outBaseDir, "bazel_testing") - } else { - cacheDir = filepath.Join(tmpDir, "bazel_testing") -@@ -441,7 +441,7 @@ func loadWorkspaceName(workspacePath string) (string, error) { - if err != nil { - return "", err - } -- nameRe := regexp.MustCompile(`(?m)^workspace\(\s*name\s*=\s*("[^"]*"|'[^']*')\s*,?\s*\)$`) -+ nameRe := regexp.MustCompile(`(?m)^workspace\(\s*name\s*=\s*("[^"]*"|'[^']*')\s*,?\s*\)\s*$`) - match := nameRe.FindSubmatchIndex(workspaceData) - if match == nil { - return "", fmt.Errorf("%s: workspace name not set", workspacePath) diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel index aab67d5d3..3d52773da 100644 --- a/tests/BUILD.bazel +++ b/tests/BUILD.bazel @@ -377,6 +377,7 @@ haskell_binary( "//tests/hackage:directory", "//tests/hackage:filepath", "//tests/hackage:process", + "//tests/integration_testing", "@stackage//:hspec", "@stackage//:hspec-core", "@stackage//:safe-exceptions", @@ -425,36 +426,6 @@ haskell_repl( ], ) -filegroup( - name = "bazel_bindist", - srcs = select({ - "@platforms//os:osx": ["@bazel_bin_darwin//file"], - "@platforms//os:linux": ["@bazel_bin_linux//file"], - "@platforms//os:windows": ["@bazel_bin_windows//file"], - }), -) - -filegroup( - name = "bazel", - testonly = True, - srcs = select({ - "//tests:nix": ["@bazel_4//:bazel_bin"], - "//conditions:default": [":bazel_bindist"], - }), - visibility = ["//tests:__subpackages__"], -) - -go_library( - name = "integration_testing", - srcs = ["integration_testing.go"], - importpath = "github.com/tweag/rules_haskell/tests/integration_testing", - visibility = ["//visibility:public"], - deps = [ - "@io_bazel_rules_go//go/tools/bazel", - "@io_bazel_rules_go//go/tools/bazel_testing", - ], -) - filegroup( name = "all_files", testonly = True, diff --git a/tests/RunTests.hs b/tests/RunTests.hs index a07c61cbd..62053463d 100644 --- a/tests/RunTests.hs +++ b/tests/RunTests.hs @@ -7,14 +7,15 @@ import Control.Exception.Safe (bracket_) import Data.Foldable (for_) import Data.List (isInfixOf, sort) import System.Directory (copyFile) -import System.Exit (ExitCode(..)) import System.FilePath (()) import System.Info (os) import System.IO.Temp (withSystemTempDirectory) import qualified System.Process as Process import Test.Hspec.Core.Spec (SpecM) -import Test.Hspec (context, hspec, it, describe, runIO, shouldSatisfy, expectationFailure) +import Test.Hspec (context, hspec, it, describe, runIO) + +import IntegrationTesting main :: IO () main = hspec $ do @@ -155,51 +156,3 @@ bazel args = Process.proc "bazel" args -- | Runs a bazel query and return the list of matching targets bazelQuery :: String -> SpecM a [String] bazelQuery q = lines <$> runIO (Process.readProcess "bazel" ["query", q] "") - --- * Action helpers - --- | Ensure that @(stdout, stderr)@ of the command satisfies a predicate -outputSatisfy - :: ((String, String) -> Bool) - -> Process.CreateProcess - -> IO () -outputSatisfy predicate cmd = do - (exitCode, stdout, stderr) <- Process.readCreateProcessWithExitCode cmd "" - - case exitCode of - ExitSuccess -> (stdout, stderr) `shouldSatisfy` predicate - ExitFailure _ -> expectationFailure (formatOutput exitCode stdout stderr) - --- | The command must succeed -assertSuccess :: Process.CreateProcess -> IO () -assertSuccess = outputSatisfy (const True) - --- | The command must fail -assertFailure :: Process.CreateProcess -> IO () -assertFailure cmd = do - (exitCode, stdout, stderr) <- Process.readCreateProcessWithExitCode cmd "" - - case exitCode of - ExitFailure _ -> pure () - ExitSuccess -> expectationFailure ("Unexpected success of a failure test with output:\n" ++ formatOutput exitCode stdout stderr) - --- * Formatting helpers - -formatOutput :: ExitCode -> String -> String -> String -formatOutput exitcode stdout stderr = - let - header = replicate 20 '-' - headerLarge = replicate 20 '=' - - in unlines [ - headerLarge - , "Exit Code: " <> show exitcode - , headerLarge - , "Standard Output" - , header - , stdout - , headerLarge - , "Error Output" - , header - , stderr - , header] diff --git a/tests/haskell_module/repl/BUILD.bazel b/tests/haskell_module/repl/BUILD.bazel index 9b92c9779..20fc3bc8a 100644 --- a/tests/haskell_module/repl/BUILD.bazel +++ b/tests/haskell_module/repl/BUILD.bazel @@ -1,27 +1,29 @@ -load("//tests:integration_tests.bzl", "integration_test") +load("//tests/integration_testing:rules_haskell_integration_test.bzl", "rules_haskell_integration_test") package(default_testonly = 1) -integration_test( +rules_haskell_integration_test( name = "haskell_module_repl_test", size = "small", - bazel = "//tests:bazel", + srcs = ["HaskellModuleReplTest.hs"], tags = [ # See https://github.com/tweag/rules_haskell/issues/1486 "dont_test_on_darwin_with_bindist", "dont_test_on_windows", ], + workspace_path = "haskell_module_repl_test", ) -integration_test( +rules_haskell_integration_test( name = "haskell_module_repl_cross_library_deps_test", size = "small", - bazel = "//tests:bazel", + srcs = ["HaskellModuleReplCrossLibraryDepsTest.hs"], tags = [ # See https://github.com/tweag/rules_haskell/issues/1486 "dont_test_on_darwin_with_bindist", "dont_test_on_windows", ], + workspace_path = "haskell_module_repl_cross_library_deps_test", ) filegroup( diff --git a/tests/haskell_module/repl/HaskellModuleReplCrossLibraryDepsTest.hs b/tests/haskell_module/repl/HaskellModuleReplCrossLibraryDepsTest.hs new file mode 100644 index 000000000..f22c8f43b --- /dev/null +++ b/tests/haskell_module/repl/HaskellModuleReplCrossLibraryDepsTest.hs @@ -0,0 +1,12 @@ +{-# OPTIONS -Wall #-} + +import Test.Hspec (hspec, it) +import IntegrationTesting + +main :: IO () +main = hspec $ do + it "bazel run repl" $ do + bazel <- setupTestBazel + let p (stdout, _stderr) = lines stdout == ["42"] + in + outputSatisfy p (bazel ["run", "//package-b:package-b@repl", "--", "-ignore-dot-ghci", "-e", "mod1num"]) diff --git a/tests/haskell_module/repl/HaskellModuleReplTest.hs b/tests/haskell_module/repl/HaskellModuleReplTest.hs new file mode 100644 index 000000000..c24a61c80 --- /dev/null +++ b/tests/haskell_module/repl/HaskellModuleReplTest.hs @@ -0,0 +1,12 @@ +{-# OPTIONS -Wall #-} + +import Test.Hspec (hspec, it) +import IntegrationTesting + +main :: IO () +main = hspec $ do + it "bazel run repl" $ do + bazel <- setupTestBazel + let p (stdout, _stderr) = lines stdout == ["420"] + in + outputSatisfy p (bazel ["run", "//:repl", "--", "-ignore-dot-ghci", "-e", "leaf"]) diff --git a/tests/haskell_module/repl/haskell_module_repl_cross_library_deps_test.go b/tests/haskell_module/repl/haskell_module_repl_cross_library_deps_test.go deleted file mode 100644 index b3d42ee93..000000000 --- a/tests/haskell_module/repl/haskell_module_repl_cross_library_deps_test.go +++ /dev/null @@ -1,183 +0,0 @@ -package haskell_module_repl_test - -import ( - bt "github.com/bazelbuild/rules_go/go/tools/bazel_testing" - it "github.com/tweag/rules_haskell/tests/integration_testing" - "testing" -) - -var testcase = ` --- WORKSPACE -- -load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") -local_repository( - name = "rules_haskell", - path = "../rules_haskell", -) - -load("@rules_haskell//haskell:repositories.bzl", "rules_haskell_dependencies") -load("@rules_haskell//tools:os_info.bzl", "os_info") - -os_info(name = "os_info") - -load("@os_info//:os_info.bzl", "is_windows") - -rules_haskell_dependencies() -load("@rules_haskell//haskell:nixpkgs.bzl", "haskell_register_ghc_nixpkgs") - -haskell_register_ghc_nixpkgs( - attribute_path = "haskell.compiler.ghc8107", - repository = "@rules_haskell//nixpkgs:default.nix", - version = "8.10.7", -) - -load("@rules_haskell//haskell:toolchain.bzl", "rules_haskell_toolchains") - -rules_haskell_toolchains(version = "8.10.7") - -load( - "@io_tweag_rules_nixpkgs//nixpkgs:nixpkgs.bzl", - "nixpkgs_cc_configure", - "nixpkgs_python_configure", - "nixpkgs_local_repository", - "nixpkgs_package", -) - -nixpkgs_cc_configure( - name = "nixpkgs_config_cc", - repository = "@rules_haskell//nixpkgs:default.nix", -) - -nixpkgs_python_configure( - repository = "@rules_haskell//nixpkgs:default.nix", -) - -nixpkgs_local_repository( - name = "nixpkgs_default", - nix_file = "@rules_haskell//nixpkgs:default.nix", -) - -load("@rules_haskell//haskell:cabal.bzl", "stack_snapshot") - -stack_snapshot( - name = "stackage", - components = {}, - local_snapshot = "@rules_haskell//:stackage_snapshot.yaml", - packages = ["base"], - stack_snapshot_json = "@rules_haskell//:stackage_snapshot.json" if not is_windows else None, - tools = [], - vendored_packages = { - "ghc-paths": "@rules_haskell//tools/ghc-paths", - }, -) --- package-a/BUILD.bazel -- -load("@rules_haskell//haskell/experimental:defs.bzl", "haskell_module") - -# Load rules_haskell rules. -load( - "@rules_haskell//haskell:defs.bzl", - "haskell_library", - "haskell_toolchain_library", -) - -package(default_visibility = ["//visibility:public"]) - -haskell_toolchain_library(name = "base") - - -# gazelle_haskell_modules:srcs: src/ -haskell_library( - name = "package-a", - ghcopts = [ - ], - modules = [ - ":package-a.PackageA.Mod1", - ":package-a.PackageA.Mod2", - ], - src_strip_prefix = "src", - deps = [ - ":base", - ], -) - -# rule generated by gazelle_haskell_modules -haskell_module( - name = "package-a.PackageA.Mod1", - src = "src/PackageA/Mod1.hs", - src_strip_prefix = "src", -) - -# rule generated by gazelle_haskell_modules -haskell_module( - name = "package-a.PackageA.Mod2", - src = "src/PackageA/Mod2.hs", - src_strip_prefix = "src", - deps = [":package-a.PackageA.Mod1"], -) - --- package-a/src/PackageA/Mod1.hs -- -module PackageA.Mod1 where - -mod1num :: Int -mod1num = 2 --- package-a/src/PackageA/Mod2.hs -- -module PackageA.Mod2 where - -import PackageA.Mod1 - -mod2num :: Int -mod2num = mod1num * 3 --- package-b/BUILD.bazel -- -load("@rules_haskell//haskell/experimental:defs.bzl", "haskell_module") - -# Load rules_haskell rules. -load( - "@rules_haskell//haskell:defs.bzl", - "haskell_library", - "haskell_toolchain_library", -) - -package(default_visibility = ["//visibility:public"]) - -haskell_toolchain_library(name = "base") - -# gazelle_haskell_modules:srcs: src/ -haskell_library( - name = "package-b", - ghcopts = [ - ], - modules = [ - ":package-b.PackageB.Mod1", - ], - narrowed_deps = ["//package-a"], - src_strip_prefix = "src", - deps = - [":base"], -) - -# rule generated by gazelle_haskell_modules -haskell_module( - name = "package-b.PackageB.Mod1", - src = "src/PackageB/Mod1.hs", - cross_library_deps = ["//package-a:package-a.PackageA.Mod1"], - src_strip_prefix = "src", -) --- package-b/src/PackageB/Mod1.hs -- -module PackageB.Mod1 (PackageB.Mod1.mod1num) where - -import qualified PackageA.Mod2 - -mod1num :: Int -mod1num = PackageA.Mod2.mod2num * 7 -` - -func TestMain(m *testing.M) { - it.TestMain(m, bt.Args{Main: testcase}) -} - -func TestHsModRepl(t *testing.T) { - out, err := it.BazelOutput(it.Context.BazelBinary, "run", "//package-b:package-b@repl", "--", "-ignore-dot-ghci", "-e", "mod1num") - if err != nil { - t.Fatal(err) - } - it.AssertOutput(t, out, "42\n") -} diff --git a/tests/haskell_module/repl/haskell_module_repl_cross_library_deps_test/WORKSPACE b/tests/haskell_module/repl/haskell_module_repl_cross_library_deps_test/WORKSPACE new file mode 100644 index 000000000..1e93438da --- /dev/null +++ b/tests/haskell_module/repl/haskell_module_repl_cross_library_deps_test/WORKSPACE @@ -0,0 +1,63 @@ +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +local_repository( + name = "rules_haskell", + path = "../rules_haskell", +) + +load("@rules_haskell//haskell:repositories.bzl", "rules_haskell_dependencies") +load("@rules_haskell//tools:os_info.bzl", "os_info") + +os_info(name = "os_info") + +load("@os_info//:os_info.bzl", "is_windows") + +rules_haskell_dependencies() + +load("@rules_haskell//haskell:nixpkgs.bzl", "haskell_register_ghc_nixpkgs") + +haskell_register_ghc_nixpkgs( + attribute_path = "haskell.compiler.ghc8107", + repository = "@rules_haskell//nixpkgs:default.nix", + version = "8.10.7", +) + +load("@rules_haskell//haskell:toolchain.bzl", "rules_haskell_toolchains") + +rules_haskell_toolchains(version = "8.10.7") + +load( + "@io_tweag_rules_nixpkgs//nixpkgs:nixpkgs.bzl", + "nixpkgs_cc_configure", + "nixpkgs_local_repository", + "nixpkgs_package", + "nixpkgs_python_configure", +) + +nixpkgs_cc_configure( + name = "nixpkgs_config_cc", + repository = "@rules_haskell//nixpkgs:default.nix", +) + +nixpkgs_python_configure( + repository = "@rules_haskell//nixpkgs:default.nix", +) + +nixpkgs_local_repository( + name = "nixpkgs_default", + nix_file = "@rules_haskell//nixpkgs:default.nix", +) + +load("@rules_haskell//haskell:cabal.bzl", "stack_snapshot") + +stack_snapshot( + name = "stackage", + components = {}, + local_snapshot = "@rules_haskell//:stackage_snapshot.yaml", + packages = ["base"], + stack_snapshot_json = "@rules_haskell//:stackage_snapshot.json" if not is_windows else None, + tools = [], + vendored_packages = { + "ghc-paths": "@rules_haskell//tools/ghc-paths", + }, +) diff --git a/tests/haskell_module/repl/haskell_module_repl_cross_library_deps_test/package-a/BUILD.bazel b/tests/haskell_module/repl/haskell_module_repl_cross_library_deps_test/package-a/BUILD.bazel new file mode 100644 index 000000000..5cc6901da --- /dev/null +++ b/tests/haskell_module/repl/haskell_module_repl_cross_library_deps_test/package-a/BUILD.bazel @@ -0,0 +1,42 @@ +load("@rules_haskell//haskell/experimental:defs.bzl", "haskell_module") + +# Load rules_haskell rules. +load( + "@rules_haskell//haskell:defs.bzl", + "haskell_library", + "haskell_toolchain_library", +) + +package(default_visibility = ["//visibility:public"]) + +haskell_toolchain_library(name = "base") + +# gazelle_haskell_modules:srcs: src/ +haskell_library( + name = "package-a", + ghcopts = [ + ], + modules = [ + ":package-a.PackageA.Mod1", + ":package-a.PackageA.Mod2", + ], + src_strip_prefix = "src", + deps = [ + ":base", + ], +) + +# rule generated by gazelle_haskell_modules +haskell_module( + name = "package-a.PackageA.Mod1", + src = "src/PackageA/Mod1.hs", + src_strip_prefix = "src", +) + +# rule generated by gazelle_haskell_modules +haskell_module( + name = "package-a.PackageA.Mod2", + src = "src/PackageA/Mod2.hs", + src_strip_prefix = "src", + deps = [":package-a.PackageA.Mod1"], +) diff --git a/tests/haskell_module/repl/haskell_module_repl_cross_library_deps_test/package-a/src/PackageA/Mod1.hs b/tests/haskell_module/repl/haskell_module_repl_cross_library_deps_test/package-a/src/PackageA/Mod1.hs new file mode 100644 index 000000000..3380c7602 --- /dev/null +++ b/tests/haskell_module/repl/haskell_module_repl_cross_library_deps_test/package-a/src/PackageA/Mod1.hs @@ -0,0 +1,4 @@ +module PackageA.Mod1 where + +mod1num :: Int +mod1num = 2 diff --git a/tests/haskell_module/repl/haskell_module_repl_cross_library_deps_test/package-a/src/PackageA/Mod2.hs b/tests/haskell_module/repl/haskell_module_repl_cross_library_deps_test/package-a/src/PackageA/Mod2.hs new file mode 100644 index 000000000..c50b54d40 --- /dev/null +++ b/tests/haskell_module/repl/haskell_module_repl_cross_library_deps_test/package-a/src/PackageA/Mod2.hs @@ -0,0 +1,6 @@ +module PackageA.Mod2 where + +import PackageA.Mod1 + +mod2num :: Int +mod2num = mod1num * 3 diff --git a/tests/haskell_module/repl/haskell_module_repl_cross_library_deps_test/package-b/BUILD.bazel b/tests/haskell_module/repl/haskell_module_repl_cross_library_deps_test/package-b/BUILD.bazel new file mode 100644 index 000000000..17f636f40 --- /dev/null +++ b/tests/haskell_module/repl/haskell_module_repl_cross_library_deps_test/package-b/BUILD.bazel @@ -0,0 +1,34 @@ +load("@rules_haskell//haskell/experimental:defs.bzl", "haskell_module") + +# Load rules_haskell rules. +load( + "@rules_haskell//haskell:defs.bzl", + "haskell_library", + "haskell_toolchain_library", +) + +package(default_visibility = ["//visibility:public"]) + +haskell_toolchain_library(name = "base") + +# gazelle_haskell_modules:srcs: src/ +haskell_library( + name = "package-b", + ghcopts = [ + ], + modules = [ + ":package-b.PackageB.Mod1", + ], + narrowed_deps = ["//package-a"], + src_strip_prefix = "src", + deps = + [":base"], +) + +# rule generated by gazelle_haskell_modules +haskell_module( + name = "package-b.PackageB.Mod1", + src = "src/PackageB/Mod1.hs", + cross_library_deps = ["//package-a:package-a.PackageA.Mod1"], + src_strip_prefix = "src", +) diff --git a/tests/haskell_module/repl/haskell_module_repl_cross_library_deps_test/package-b/src/PackageB/Mod1.hs b/tests/haskell_module/repl/haskell_module_repl_cross_library_deps_test/package-b/src/PackageB/Mod1.hs new file mode 100644 index 000000000..b8a4e376f --- /dev/null +++ b/tests/haskell_module/repl/haskell_module_repl_cross_library_deps_test/package-b/src/PackageB/Mod1.hs @@ -0,0 +1,6 @@ +module PackageB.Mod1 (PackageB.Mod1.mod1num) where + +import qualified PackageA.Mod2 + +mod1num :: Int +mod1num = PackageA.Mod2.mod2num * 7 diff --git a/tests/haskell_module/repl/haskell_module_repl_test.go b/tests/haskell_module/repl/haskell_module_repl_test.go deleted file mode 100644 index 93004d198..000000000 --- a/tests/haskell_module/repl/haskell_module_repl_test.go +++ /dev/null @@ -1,164 +0,0 @@ -package haskell_module_repl_test - -import ( - bt "github.com/bazelbuild/rules_go/go/tools/bazel_testing" - it "github.com/tweag/rules_haskell/tests/integration_testing" - "testing" -) - -var testcase = ` --- WORKSPACE -- -load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") -local_repository( - name = "rules_haskell", - path = "../rules_haskell", -) - -load("@rules_haskell//haskell:repositories.bzl", "rules_haskell_dependencies") -load("@rules_haskell//tools:os_info.bzl", "os_info") - -os_info(name = "os_info") - -load("@os_info//:os_info.bzl", "is_windows") - -rules_haskell_dependencies() -load("@rules_haskell//haskell:nixpkgs.bzl", "haskell_register_ghc_nixpkgs") - -haskell_register_ghc_nixpkgs( - attribute_path = "haskell.compiler.ghc8107", - repository = "@rules_haskell//nixpkgs:default.nix", - version = "8.10.7", -) - -load("@rules_haskell//haskell:toolchain.bzl", "rules_haskell_toolchains") - -rules_haskell_toolchains(version = "8.10.7") - -load( - "@io_tweag_rules_nixpkgs//nixpkgs:nixpkgs.bzl", - "nixpkgs_cc_configure", - "nixpkgs_python_configure", - "nixpkgs_local_repository", - "nixpkgs_package", -) - -nixpkgs_cc_configure( - name = "nixpkgs_config_cc", - repository = "@rules_haskell//nixpkgs:default.nix", -) - -nixpkgs_python_configure( - repository = "@rules_haskell//nixpkgs:default.nix", -) - -nixpkgs_local_repository( - name = "nixpkgs_default", - nix_file = "@rules_haskell//nixpkgs:default.nix", -) - -load("@rules_haskell//haskell:cabal.bzl", "stack_snapshot") - -stack_snapshot( - name = "stackage", - components = {}, - local_snapshot = "@rules_haskell//:stackage_snapshot.yaml", - packages = ["base"], - stack_snapshot_json = "@rules_haskell//:stackage_snapshot.json" if not is_windows else None, - tools = [], - vendored_packages = { - "ghc-paths": "@rules_haskell//tools/ghc-paths", - }, -) --- BUILD.bazel -- -"""Test compilation of a multiple interdependent Haskell modules with only core-package dependencies.""" - -load("@rules_haskell//haskell:defs.bzl", "haskell_library", "haskell_repl", "haskell_toolchain_library") -load("@rules_haskell//haskell/experimental:defs.bzl", "haskell_module") - -haskell_toolchain_library(name = "base") - -haskell_repl( - name = "repl", - deps = [":lib"], -) - -haskell_module( - name = "root", - src = "Root.hs", -) - -haskell_module( - name = "branch_left", - src = "BranchLeft.hs", - deps = [ - ":root", - ], -) - -haskell_module( - name = "branch_right", - src = "BranchRight.hs", - deps = [ - ":root", - ], -) - -haskell_module( - name = "leaf", - src = "Leaf.hs", - deps = [ - ":branch_left", - ":branch_right", - ], -) - -haskell_library( - name = "lib", - modules = [ - ":root", - ":branch_left", - ":branch_right", - ":leaf", - ], - deps = [":base"], -) --- BranchLeft.hs -- -module BranchLeft where - -import Root - -branch_left :: Int -branch_left = 3 * root --- BranchRight.hs -- -module BranchRight where - -import Root - -branch_right :: Int -branch_right = 5 * root --- Leaf.hs -- -module Leaf where - -import BranchLeft -import BranchRight - -leaf :: Int -leaf = 7 * branch_left * branch_right --- Root.hs -- -module Root where - -root :: Int -root = 2 -` - -func TestMain(m *testing.M) { - it.TestMain(m, bt.Args{Main: testcase}) -} - -func TestHsModRepl(t *testing.T) { - out, err := it.BazelOutput(it.Context.BazelBinary, "run", "//:repl", "--", "-ignore-dot-ghci", "-e", "leaf") - if err != nil { - t.Fatal(err) - } - it.AssertOutput(t, out, "420\n") -} diff --git a/tests/haskell_module/repl/haskell_module_repl_test/BUILD.bazel b/tests/haskell_module/repl/haskell_module_repl_test/BUILD.bazel new file mode 100644 index 000000000..ffb1d1004 --- /dev/null +++ b/tests/haskell_module/repl/haskell_module_repl_test/BUILD.bazel @@ -0,0 +1,52 @@ +"""Test compilation of a multiple interdependent Haskell modules with only core-package dependencies.""" + +load("@rules_haskell//haskell:defs.bzl", "haskell_library", "haskell_repl", "haskell_toolchain_library") +load("@rules_haskell//haskell/experimental:defs.bzl", "haskell_module") + +haskell_toolchain_library(name = "base") + +haskell_repl( + name = "repl", + deps = [":lib"], +) + +haskell_module( + name = "root", + src = "Root.hs", +) + +haskell_module( + name = "branch_left", + src = "BranchLeft.hs", + deps = [ + ":root", + ], +) + +haskell_module( + name = "branch_right", + src = "BranchRight.hs", + deps = [ + ":root", + ], +) + +haskell_module( + name = "leaf", + src = "Leaf.hs", + deps = [ + ":branch_left", + ":branch_right", + ], +) + +haskell_library( + name = "lib", + modules = [ + ":root", + ":branch_left", + ":branch_right", + ":leaf", + ], + deps = [":base"], +) diff --git a/tests/haskell_module/repl/haskell_module_repl_test/BranchLeft.hs b/tests/haskell_module/repl/haskell_module_repl_test/BranchLeft.hs new file mode 100644 index 000000000..827d78385 --- /dev/null +++ b/tests/haskell_module/repl/haskell_module_repl_test/BranchLeft.hs @@ -0,0 +1,6 @@ +module BranchLeft where + +import Root + +branch_left :: Int +branch_left = 3 * root diff --git a/tests/haskell_module/repl/haskell_module_repl_test/BranchRight.hs b/tests/haskell_module/repl/haskell_module_repl_test/BranchRight.hs new file mode 100644 index 000000000..1bd31b23d --- /dev/null +++ b/tests/haskell_module/repl/haskell_module_repl_test/BranchRight.hs @@ -0,0 +1,6 @@ +module BranchRight where + +import Root + +branch_right :: Int +branch_right = 5 * root diff --git a/tests/haskell_module/repl/haskell_module_repl_test/Leaf.hs b/tests/haskell_module/repl/haskell_module_repl_test/Leaf.hs new file mode 100644 index 000000000..022f9e922 --- /dev/null +++ b/tests/haskell_module/repl/haskell_module_repl_test/Leaf.hs @@ -0,0 +1,7 @@ +module Leaf where + +import BranchLeft +import BranchRight + +leaf :: Int +leaf = 7 * branch_left * branch_right diff --git a/tests/haskell_module/repl/haskell_module_repl_test/Root.hs b/tests/haskell_module/repl/haskell_module_repl_test/Root.hs new file mode 100644 index 000000000..a07015384 --- /dev/null +++ b/tests/haskell_module/repl/haskell_module_repl_test/Root.hs @@ -0,0 +1,4 @@ +module Root where + +root :: Int +root = 2 diff --git a/tests/haskell_module/repl/haskell_module_repl_test/WORKSPACE b/tests/haskell_module/repl/haskell_module_repl_test/WORKSPACE new file mode 100644 index 000000000..1e93438da --- /dev/null +++ b/tests/haskell_module/repl/haskell_module_repl_test/WORKSPACE @@ -0,0 +1,63 @@ +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +local_repository( + name = "rules_haskell", + path = "../rules_haskell", +) + +load("@rules_haskell//haskell:repositories.bzl", "rules_haskell_dependencies") +load("@rules_haskell//tools:os_info.bzl", "os_info") + +os_info(name = "os_info") + +load("@os_info//:os_info.bzl", "is_windows") + +rules_haskell_dependencies() + +load("@rules_haskell//haskell:nixpkgs.bzl", "haskell_register_ghc_nixpkgs") + +haskell_register_ghc_nixpkgs( + attribute_path = "haskell.compiler.ghc8107", + repository = "@rules_haskell//nixpkgs:default.nix", + version = "8.10.7", +) + +load("@rules_haskell//haskell:toolchain.bzl", "rules_haskell_toolchains") + +rules_haskell_toolchains(version = "8.10.7") + +load( + "@io_tweag_rules_nixpkgs//nixpkgs:nixpkgs.bzl", + "nixpkgs_cc_configure", + "nixpkgs_local_repository", + "nixpkgs_package", + "nixpkgs_python_configure", +) + +nixpkgs_cc_configure( + name = "nixpkgs_config_cc", + repository = "@rules_haskell//nixpkgs:default.nix", +) + +nixpkgs_python_configure( + repository = "@rules_haskell//nixpkgs:default.nix", +) + +nixpkgs_local_repository( + name = "nixpkgs_default", + nix_file = "@rules_haskell//nixpkgs:default.nix", +) + +load("@rules_haskell//haskell:cabal.bzl", "stack_snapshot") + +stack_snapshot( + name = "stackage", + components = {}, + local_snapshot = "@rules_haskell//:stackage_snapshot.yaml", + packages = ["base"], + stack_snapshot_json = "@rules_haskell//:stackage_snapshot.json" if not is_windows else None, + tools = [], + vendored_packages = { + "ghc-paths": "@rules_haskell//tools/ghc-paths", + }, +) diff --git a/tests/integration_testing.go b/tests/integration_testing.go deleted file mode 100644 index b9c5e43eb..000000000 --- a/tests/integration_testing.go +++ /dev/null @@ -1,156 +0,0 @@ -package integration_testing - -import ( - "bytes" - "fmt" - "github.com/bazelbuild/rules_go/go/tools/bazel" - "github.com/bazelbuild/rules_go/go/tools/bazel_testing" - "os" - "os/exec" - "runtime" - "strings" - "testing" -) - -func TestMain(m *testing.M, args bazel_testing.Args) { - if err := ParseArgs(); err != nil { - fmt.Fprint(os.Stderr, err) - return - } - defer exec.Command(Context.BazelBinary, "shutdown") - - args.Main += GenerateBazelrc() - bazel_testing.TestMain(m, args) -} - -func AssertOutput(t *testing.T, output []byte, expected string) { - if string(output) != expected { - t.Fatalf("output of bazel process is invalid.\n%-10s%v\n%-10s%v\n", "Expected:", expected, "Actual:", string(output)) - } -} - -var Context struct { - Nixpkgs bool - BazelBinary string -} - -func ParseArgs() error { - bazelPath := "" - for _, arg := range os.Args { - if strings.HasPrefix(arg, "nixpkgs=") { - fmt.Sscanf(arg, "nixpkgs=%t", &Context.Nixpkgs) - } else if strings.HasPrefix(arg, "bazel_bin=") { - fmt.Sscanf(arg, "bazel_bin=%s", &bazelPath) - } - } - bazelAbsPath, err := bazel.Runfile(bazelPath) - Context.BazelBinary = bazelAbsPath - return err -} - -func GenerateBazelrc() string { - bazelrc := ` --- .bazelrc -- -build:linux-nixpkgs --host_platform=@io_tweag_rules_nixpkgs//nixpkgs/platforms:host -build:linux-nixpkgs --incompatible_enable_cc_toolchain_resolution -build:macos-nixpkgs --host_platform=@io_tweag_rules_nixpkgs//nixpkgs/platforms:host -build:macos-nixpkgs --incompatible_enable_cc_toolchain_resolution -build:linux-bindist --incompatible_enable_cc_toolchain_resolution -build:macos-bindist --incompatible_enable_cc_toolchain_resolution -build:windows-bindist --crosstool_top=@rules_haskell_ghc_windows_amd64//:cc_toolchain -` - if bazel_testing.OutputUserRoot != "" { - bazelrc += fmt.Sprintf("startup: --output_user_root=%s\n", bazel_testing.OutputUserRoot) - } - return bazelrc -} - -func BazelEnv() []string { - env := []string{} - // It's important that the value of $HOME is invariant between different integration test runs - // and that the directory is writable for bazel test. - env = append(env, fmt.Sprintf("HOME=%s", os.TempDir())) - if runtime.GOOS == "darwin" { - env = append(env, "BAZEL_USE_CPP_ONLY_TOOLCHAIN=1") - if Context.Nixpkgs { - env = append(env, "BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1") - } - } - for _, e := range os.Environ() { - // Filter environment variables set by the bazel test wrapper script. - // These confuse recursive invocations of Bazel. - if strings.HasPrefix(e, "TEST_") || strings.HasPrefix(e, "RUNFILES_") { - continue - } - env = append(env, e) - } - return env -} - -func insertBazelFlags(args []string, flags ...string) []string { - for i, arg := range args { - switch arg { - case "build", "test", "run": - return append(append(append([]string{}, args[:i+1]...), flags...), args[i+1:]...) - default: - continue - } - } - return args -} - -func BazelConfig() string { - switch os := runtime.GOOS; os { - case "linux": - if Context.Nixpkgs { - return "linux-nixpkgs" - } else { - return "linux-bindist" - } - case "darwin": - if Context.Nixpkgs { - return "macos-nixpkgs" - } else { - return "macos-bindist" - } - case "windows": - return "windows-bindist" - default: - panic(fmt.Sprintf("Unknown OS name: %s", os)) - } -} - -func BazelCmd(bazelPath string, args ...string) *exec.Cmd { - cmd := exec.Command(bazelPath) - args = insertBazelFlags(args, "--config", BazelConfig()) - cmd.Args = append(cmd.Args, args...) - cmd.Env = append(cmd.Env, BazelEnv()...) - return cmd -} - -func RunBazel(bazelPath string, args ...string) error { - cmd := BazelCmd(bazelPath, args...) - - buf := &bytes.Buffer{} - cmd.Stderr = buf - err := cmd.Run() - if eErr, ok := err.(*exec.ExitError); ok { - eErr.Stderr = buf.Bytes() - err = &bazel_testing.StderrExitError{Err: eErr} - } - return err -} - -func BazelOutput(bazelPath string, args ...string) ([]byte, error) { - cmd := BazelCmd(bazelPath, args...) - stdout := &bytes.Buffer{} - stderr := &bytes.Buffer{} - cmd.Stdout = stdout - cmd.Stderr = stderr - err := cmd.Run() - if eErr, ok := err.(*exec.ExitError); ok { - eErr.Stderr = stderr.Bytes() - err = &bazel_testing.StderrExitError{Err: eErr} - } - return stdout.Bytes(), err -} diff --git a/tests/integration_testing/BUILD.bazel b/tests/integration_testing/BUILD.bazel new file mode 100644 index 000000000..722568ca1 --- /dev/null +++ b/tests/integration_testing/BUILD.bazel @@ -0,0 +1,16 @@ +load("@rules_haskell//haskell:defs.bzl", "haskell_library") + +haskell_library( + name = "integration_testing", + srcs = ["IntegrationTesting.hs"], + visibility = ["//tests:__subpackages__"], + deps = [ + "//tests/hackage:base", + "//tests/hackage:directory", + "//tests/hackage:filepath", + "//tests/hackage:process", + "//tests/hackage:text", + "@stackage//:hspec", + "@stackage//:hspec-core", + ], +) diff --git a/tests/integration_testing/IntegrationTesting.hs b/tests/integration_testing/IntegrationTesting.hs new file mode 100644 index 000000000..9b86c363f --- /dev/null +++ b/tests/integration_testing/IntegrationTesting.hs @@ -0,0 +1,174 @@ +module IntegrationTesting + ( bazelCmd + , setupWorkspace + , setupTestBazel + , assertSuccess + , assertFailure + , outputSatisfy + , failedOutputSatisfy + , formatOutput + ) where + +import qualified System.Process as Process +import System.Environment (getEnv, unsetEnv, lookupEnv) +import System.Info (os) +import System.FilePath (pathSeparators, ()) +import System.Directory (copyFile, doesDirectoryExist, doesFileExist, removePathForcibly, createDirectory, listDirectory, doesPathExist) +import Data.Text (pack, unpack, replace, breakOn) +import Data.Text.IO (readFile, writeFile) +import System.Exit (ExitCode(..)) +import Control.Monad (when, unless, forM_) +import Test.Hspec (shouldSatisfy, expectationFailure) + +bazelCmd :: String -> String -> IO ([String] -> Process.CreateProcess) +bazelCmd workspaceDir outputUserRoot = do + bazelPath <- getEnv "BIT_BAZEL_BINARY" + config <- (fmap bazelConfig isNixpkgs) + let bazelConfigurableSubcommands = + ["aquery", "build", "canonicalize-flags", "clean", "coverage", "cquery", "info", "mobile-install", "print_action", "run", "test"] + return (\args -> case args of + subcommand:xs | elem subcommand bazelConfigurableSubcommands -> (Process.proc bazelPath (["--output_user_root", outputUserRoot, subcommand, "--config", config] ++ xs)) { Process.cwd = Just workspaceDir } + xs -> (Process.proc bazelPath (["--output_user_root", outputUserRoot] ++ xs)) { Process.cwd = Just workspaceDir }) + +isNixpkgs :: IO Bool +isNixpkgs = lookupEnv "NIXPKGS" >>= \value -> + case value of + Just "1" -> pure True + _ -> pure False + +bazelConfig :: Bool -> String +bazelConfig isnix + | isnix = case os of + "darwin" -> "macos-nixpkgs" + _ -> "linux-nixpkgs" + | otherwise = case os of + "darwin" -> "macos-bindist" + "mingw32" -> "windows-bindist" + _ -> "linux-bindist" + +outputBaseDir :: IO String +outputBaseDir = do + tmpDir <- getEnv "TEST_TMPDIR" + return (unpack . fst $ breakOn (pack (pathSeparators ++ "execroot" ++ pathSeparators)) (pack tmpDir)) + +replaceInFile :: FilePath -> String -> String -> IO () +replaceInFile path from to = do + content <- Data.Text.IO.readFile path + Data.Text.IO.writeFile path (replace (pack from) (pack to) content) + +removeDirIfExist :: FilePath -> IO () +removeDirIfExist path = do + dirExist <- doesDirectoryExist path + when dirExist (removePathForcibly path) + +createDirIfNotExist :: FilePath -> IO () +createDirIfNotExist path = do + dirExist <- doesDirectoryExist path + unless dirExist (createDirectory path) + +generateBazelRc :: FilePath -> IO () +generateBazelRc dir = do + alreadyExist <- doesFileExist (dir ".bazelrc") + unless alreadyExist $ Data.Text.IO.writeFile (dir ".bazelrc") (pack " \n\ +\ build:linux-nixpkgs --host_platform=@io_tweag_rules_nixpkgs//nixpkgs/platforms:host \n\ +\ build:linux-nixpkgs --incompatible_enable_cc_toolchain_resolution \n\ +\ build:macos-nixpkgs --host_platform=@io_tweag_rules_nixpkgs//nixpkgs/platforms:host \n\ +\ build:macos-nixpkgs --incompatible_enable_cc_toolchain_resolution \n\ +\ build:linux-bindist --incompatible_enable_cc_toolchain_resolution \n\ +\ build:macos-bindist --incompatible_enable_cc_toolchain_resolution \n\ +\ build:windows-bindist --crosstool_top=@rules_haskell_ghc_windows_amd64//:cc_toolchain \n\ +\ ") + +setupWorkspace :: IO (String, String) +setupWorkspace = do + workspaceDir <- getEnv "BIT_WORKSPACE_DIR" + outputBase <- outputBaseDir + runfilesDir <- getEnv "RUNFILES_DIR" + let execDir = outputBase "bazel_testing" + createDirIfNotExist execDir + let newWorkspaceDir = execDir "main" + let outputUserRoot = outputBase + removeDirIfExist newWorkspaceDir + copyDirectoryRecursive workspaceDir newWorkspaceDir + generateBazelRc newWorkspaceDir + removeDirIfExist (execDir "rules_haskell") + copyDirectoryRecursive (runfilesDir "rules_haskell") (execDir "rules_haskell") + replaceInFile (newWorkspaceDir "WORKSPACE") "%RULES_HASKELL_PATH%" "../rules_haskell" + return (newWorkspaceDir, outputUserRoot) + +setupTestBazel :: IO ([String] -> Process.CreateProcess) +setupTestBazel = setupWorkspace >>= uncurry bazelCmd + +-- * Action helpers + +-- | Ensure that @(stdout, stderr)@ of the command satisfies a predicate +outputSatisfy + :: ((String, String) -> Bool) + -> Process.CreateProcess + -> IO () +outputSatisfy predicate cmd = do + (exitCode, stdout, stderr) <- Process.readCreateProcessWithExitCode cmd "" + + case exitCode of + ExitSuccess -> (stdout, stderr) `shouldSatisfy` predicate + ExitFailure _ -> expectationFailure (formatOutput exitCode stdout stderr) + +-- | Ensure that command is failed and @(stdout, stderr)@ of the command satisfies a predicate +failedOutputSatisfy + :: ((String, String) -> Bool) + -> Process.CreateProcess + -> IO () +failedOutputSatisfy predicate cmd = do + (exitCode, stdout, stderr) <- Process.readCreateProcessWithExitCode cmd "" + + case exitCode of + ExitFailure _ -> (stdout, stderr) `shouldSatisfy` predicate + ExitSuccess -> expectationFailure (formatOutput exitCode stdout stderr) + +-- | The command must succeed +assertSuccess :: Process.CreateProcess -> IO () +assertSuccess = outputSatisfy (const True) + +-- | The command must fail +assertFailure :: Process.CreateProcess -> IO () +assertFailure cmd = do + (exitCode, stdout, stderr) <- Process.readCreateProcessWithExitCode cmd "" + + case exitCode of + ExitFailure _ -> pure () + ExitSuccess -> expectationFailure ("Unexpected success of a failure test with output:\n" ++ formatOutput exitCode stdout stderr) + +-- * Formatting helpers + +formatOutput :: ExitCode -> String -> String -> String +formatOutput exitcode stdout stderr = + let + header = replicate 20 '-' + headerLarge = replicate 20 '=' + + in unlines [ + headerLarge + , "Exit Code: " <> show exitcode + , headerLarge + , "Standard Output" + , header + , stdout + , headerLarge + , "Error Output" + , header + , stderr + , header] + +copyDirectoryRecursive :: FilePath -> FilePath -> IO () +copyDirectoryRecursive srcDir dstDir = do + unlessM (doesPathExist dstDir) (createDirectory dstDir) + entries <- listDirectory srcDir + forM_ entries $ \name -> do + let srcPath = srcDir name + let dstPath = dstDir name + isDir <- doesDirectoryExist srcPath + if isDir + then copyDirectoryRecursive srcPath dstPath + else copyFile srcPath dstPath + where + unlessM b f = do b <- b; if b then pure() else f diff --git a/tests/integration_testing/README.md b/tests/integration_testing/README.md new file mode 100644 index 000000000..3d7fe1bd9 --- /dev/null +++ b/tests/integration_testing/README.md @@ -0,0 +1,111 @@ +# `rules_haskell` integration testing + +This package provides a set of rules for writing test scenarios for `rules_haskell`'s rules testing. It supports: + * Creating test workspaces inside the `rules_haskell` codebase + * Writing test scenarios in Haskell and provides a small testing library with helper functions + * Running tests against multiple bazel verions including binary bazel distributions and nixpkgs bazel packages + * Reusing the bazel cache between test runs to significantly speedup test process + +## How to write an integration test + +There is a `rules_haskell_integration_test` rule which allows you to create a test workspace and +write a test-scenario in Haskell. + +### Create test workspace + +First you need to create a directory for the test workspace: + +``` +some-tests-dir +| ++-- some_test_workspace +| | +| +-- WORKSPACE +| +-- BUILD.bazel +| +-- # some other stuff needed in your test +| ++-- BUILD.bazel +| ++-- SomeTest.hs +... +``` +If you want to refer to `rules_haskell` as a dependency you should put this into your test workspace: +``` +local_repository( + name = "rules_haskell", + path = "%RULES_HASKELL_PATH%" +) +``` +The exact path will be set by the test suite. + +### Write a test scenario + +The test scenario in this example is described in the `SomeTest.hs` file. It is supposed to be a runnable binary which indicates failure or success with its exit status. +There is an `IntegrationTesting` library which provides some useful function to setup the test environment, run bazel commands, and test the output conditions. +The most important of them are: + + 1. `setupWorkspace` - prepares a directory structure for the test: directory for test workspace, for bazel output and for `rules_haskell` + returns a tuple of path to test workspace and path to bazel output + 2. `bazelCmd workspaceDir bazelOutputDir` - returns a function which creates a process from bazel arguments with respect to test workspace, bazel output and platform configurations + 3. `setupTestBazel` - is a combination of the two functions above which prepares the workspace and returns the bazel-function applied to this workspace + +Also there is a bunch of functions for asserting bazel process and it's output on various conditions. +So for example we can create the simplest scenario for `SomeTest.hs`: + +`SomeTest.hs` +```haskell +import Test.Hspec (hspec, it) +import IntegrationTesting + +main = hspec $ do + it "bazel test" $ do + bazel <- setupTestBazel + assertSuccess $ bazel ["test", "//..."] +``` + +### Describe test rule + +Next you need to put an instance of `rules_haskell_integration_test` in `some-tests-dir/BUILD.bazel` + +`BUILD.bazel` +```bzl +load("//tests/integration_testing:rules_haskell_integration_test.bzl", "rules_haskell_integration_test") + +rules_haskell_integration_test( + name = "some_test", + srcs = ["SomeTest.hs"], + workspace_path = "some_test", +) +``` +By default it will create a test for every bazel version specified in the `SUPPORTED_BAZEL_VERSIONS`-variable defined in `//:bazel_versions.bzl`, and for every bazel nixpkgs package specified in the `SUPPORTED_BAZEL_NIXPKGS_VERSIONS`-variable. To limit the list of bazel versions applied to a test one can use the `bindist_bazel_versions` and `nixpkgs_bazel_packages` parameters. + +### Update deleted packages + +Since test workspaces contains `WORKSPACE` files it should be ignored by bazel. In order to achieve that one should run +``` +bazel run @contrib_rules_bazel_integration_test//tools:update_deleted_packages +``` +which will update `.bazelrc` with new `deleted_packages` flags. One should push these `.bazelrc` changes with the test. Otherwise, the new test will fail with something like +``` +Error in fail: Can not find specified file in [ +... +] +``` + +### Run test + +`rules_haskell_integration_test` will create a target for every bazel version it uses with a specific name pattern. For example the following instance of the macro +``` +rules_haskell_integration_test( + name = "some_test", + srcs = ["SomeTest.hs"], + workspace_path = "some_test", + bindist_bazel_versions = ["4.1.0"], + nixpkgs_bazel_package = ["bazel_4"], +) +``` +will create two test targets: `some_test_bindist_4.1.0` and `some_test_nixpkgs_bazel_4` +Also, every integration test gets the `integration` tag by default, so one can either refer to the test by name or just run +``` +bazel test --test_tag_filters=integration //... +``` diff --git a/tests/integration_testing/dependencies.bzl b/tests/integration_testing/dependencies.bzl new file mode 100644 index 000000000..80015854b --- /dev/null +++ b/tests/integration_testing/dependencies.bzl @@ -0,0 +1,26 @@ +load("@io_tweag_rules_nixpkgs//nixpkgs:nixpkgs.bzl", "nixpkgs_package") +load("@contrib_rules_bazel_integration_test//bazel_integration_test:defs.bzl", "bazel_binaries") +load( + "//:bazel_versions.bzl", + "SUPPORTED_BAZEL_VERSIONS", + "SUPPORTED_NIXPKGS_BAZEL_PACKAGES", +) + +def integration_testing_bazel_binaries(): + bazel_binaries(versions = SUPPORTED_BAZEL_VERSIONS) + for package in SUPPORTED_NIXPKGS_BAZEL_PACKAGES: + nixpkgs_package( + name = package, + repository = "@nixpkgs_default", + build_file_content = """\ +filegroup( + name = "bazel_bin", + srcs = ["bin/bazel"], + visibility = [ "//visibility:public" ], +) +""", + fail_not_supported = False, + ) + +def nixpkgs_bazel_label(package): + return "@%s//:bazel_bin" % package diff --git a/tests/integration_testing/haskell_bazel_integration_test.bzl b/tests/integration_testing/haskell_bazel_integration_test.bzl new file mode 100644 index 000000000..92987605b --- /dev/null +++ b/tests/integration_testing/haskell_bazel_integration_test.bzl @@ -0,0 +1,51 @@ +load("@rules_haskell//haskell:defs.bzl", "haskell_binary") +load( + "@contrib_rules_bazel_integration_test//bazel_integration_test:defs.bzl", + "bazel_integration_test", + "integration_test_utils", +) +load("@cgrindel_bazel_starlib//shlib/rules:execute_binary.bzl", "execute_binary") + +DEFAULT_TAGS = ["exclusive", "integration"] +DEFAULT_HASKELL_DEPS = [ + "//tests/integration_testing", + "//tests/hackage:base", + "@stackage//:hspec", + "@stackage//:hspec-core", +] + +def haskell_bazel_integration_test( + name, + srcs, + bazel_binaries, + workspace_path, + args = [], + deps = [], + rule_files = [], + **kwargs): + kwargs["tags"] = kwargs.pop("tags", []) + DEFAULT_TAGS + + binary_name = "%s_bin" % name + haskell_binary( + name = binary_name, + srcs = srcs, + deps = deps + DEFAULT_HASKELL_DEPS, + testonly = True, + ) + + runner_name = "%s_wrapped" % binary_name + execute_binary( + name = runner_name, + binary = binary_name, + arguments = args, + ) + + for bazel_name, bazel_binary in bazel_binaries.items(): + bazel_integration_test( + name = "%s_%s" % (name, bazel_name), + test_runner = runner_name, + bazel_binary = bazel_binary, + workspace_files = integration_test_utils.glob_workspace_files(workspace_path) + rule_files, + workspace_path = workspace_path, + **kwargs + ) diff --git a/tests/integration_testing/rules_haskell_integration_test.bzl b/tests/integration_testing/rules_haskell_integration_test.bzl new file mode 100644 index 000000000..1539f597e --- /dev/null +++ b/tests/integration_testing/rules_haskell_integration_test.bzl @@ -0,0 +1,63 @@ +load(":haskell_bazel_integration_test.bzl", "haskell_bazel_integration_test") +load( + "@contrib_rules_bazel_integration_test//bazel_integration_test:defs.bzl", + "integration_test_utils", +) +load( + "//:bazel_versions.bzl", + "SUPPORTED_BAZEL_VERSIONS", + "SUPPORTED_NIXPKGS_BAZEL_PACKAGES", +) +load("//tests/integration_testing:dependencies.bzl", "nixpkgs_bazel_label") +load("@os_info//:os_info.bzl", "is_nix_shell", "is_windows") + +def rules_haskell_integration_test( + name, + workspace_path, + srcs, + deps = [], + bindist_bazel_versions = SUPPORTED_BAZEL_VERSIONS, + nixpkgs_bazel_packages = SUPPORTED_NIXPKGS_BAZEL_PACKAGES, + **kwargs): + bindist_bazel_binaries = { + version: integration_test_utils.bazel_binary_label(version) + for version in bindist_bazel_versions + } + nixpkgs_bazel_binaries = { + package: nixpkgs_bazel_label(package) + for package in nixpkgs_bazel_packages + } + + if is_nix_shell: + haskell_bazel_integration_test( + name = "%s_nixpkgs" % name, + srcs = srcs, + deps = deps, + env = { + "NIXPKGS": "1", + }, + bazel_binaries = nixpkgs_bazel_binaries, + workspace_path = workspace_path, + rule_files = ["//:distribution"], + target_compatible_with = select({ + "//tests:nix": [], + "//conditions:default": ["@platforms//:incompatible"], + }), + additional_env_inherit = ["BAZEL_USE_CPP_ONLY_TOOLCHAIN"], + **kwargs + ) + elif not is_windows: + haskell_bazel_integration_test( + name = "%s_bindist" % name, + srcs = srcs, + deps = deps, + bazel_binaries = bindist_bazel_binaries, + workspace_path = workspace_path, + rule_files = ["//:distribution"], + target_compatible_with = select({ + "//tests:nix": ["@platforms//:incompatible"], + "//conditions:default": [], + }), + additional_env_inherit = ["BAZEL_USE_CPP_ONLY_TOOLCHAIN"], + **kwargs + ) diff --git a/tests/integration_tests.bzl b/tests/integration_tests.bzl deleted file mode 100644 index 4e518a2b9..000000000 --- a/tests/integration_tests.bzl +++ /dev/null @@ -1,23 +0,0 @@ -load("@io_bazel_rules_go//go/tools/bazel_testing:def.bzl", "go_bazel_test") - -def integration_test(name, bazel, **kwargs): - test_src = name + ".go" - size = kwargs.pop("size", "medium") - kwargs.setdefault("deps", []) - - it_library = "@rules_haskell//tests:integration_testing" - if it_library not in kwargs["deps"]: - kwargs["deps"].append(it_library) - - go_bazel_test( - name = name, - srcs = [test_src], - size = size, - rule_files = ["//:distribution"], - args = select({ - "//tests:nix": ["nixpkgs=true"], - "//conditions:default": ["nixpkgs=false"], - }) + ["bazel_bin=$(location {})".format(bazel)], - data = [bazel] + kwargs.pop("data", []), - **kwargs - ) diff --git a/tests/repl-targets/BUILD.bazel b/tests/repl-targets/BUILD.bazel index 9a9a37e07..8b88498f6 100644 --- a/tests/repl-targets/BUILD.bazel +++ b/tests/repl-targets/BUILD.bazel @@ -1,11 +1,11 @@ load("@rules_haskell//haskell:c2hs.bzl", "c2hs_library") load( "@rules_haskell//haskell:defs.bzl", + "haskell_binary", "haskell_library", "haskell_test", ) -load("@io_bazel_rules_go//go/tools/bazel_testing:def.bzl", "go_bazel_test") -load("//tests:integration_tests.bzl", "integration_test") +load("//tests/integration_testing:rules_haskell_integration_test.bzl", "rules_haskell_integration_test") package(default_testonly = 1) @@ -114,21 +114,21 @@ haskell_library( deps = ["//tests/hackage:base"], ) -integration_test( +rules_haskell_integration_test( name = "hs_bin_repl_test", - size = "medium", - bazel = "//tests:bazel", + srcs = ["HsBinReplTest.hs"], + workspace_path = "hs_bin_repl_test", ) -integration_test( +rules_haskell_integration_test( name = "hs_lib_repl_test", - size = "large", - bazel = "//tests:bazel", + srcs = ["HsLibReplTest.hs"], tags = [ # See https://github.com/tweag/rules_haskell/issues/1486 "dont_test_on_darwin_with_bindist", "dont_test_on_windows", ], + workspace_path = "hs_lib_repl_test", ) filegroup( diff --git a/tests/repl-targets/HsBinReplTest.hs b/tests/repl-targets/HsBinReplTest.hs new file mode 100644 index 000000000..967e705ed --- /dev/null +++ b/tests/repl-targets/HsBinReplTest.hs @@ -0,0 +1,12 @@ +{-# OPTIONS -Wall #-} + +import Test.Hspec (hspec, it) +import IntegrationTesting + +main :: IO () +main = hspec $ do + it "bazel run repl" $ do + bazel <- setupTestBazel + let p (stdout, _stderr) = lines stdout == ["Hello GHCi!"] + in + outputSatisfy p (bazel ["run", "//:hs-bin@repl", "--", "-ignore-dot-ghci", "-e", ":main"]) diff --git a/tests/repl-targets/HsLibReplTest.hs b/tests/repl-targets/HsLibReplTest.hs new file mode 100644 index 000000000..22c5b8fa9 --- /dev/null +++ b/tests/repl-targets/HsLibReplTest.hs @@ -0,0 +1,12 @@ +{-# OPTIONS -Wall #-} + +import Test.Hspec (hspec, it) +import IntegrationTesting + +main :: IO () +main = hspec $ do + it "bazel run repl" $ do + bazel <- setupTestBazel + let p (stdout, _stderr) = lines stdout == ["\"16barbazgen\""] + in + outputSatisfy p (bazel ["run", "//:hs-lib@repl", "--", "-ignore-dot-ghci", "-e", "show (foo 10) ++ bar ++ baz ++ gen"]) diff --git a/tests/repl-targets/hs_bin_repl_test.go b/tests/repl-targets/hs_bin_repl_test.go deleted file mode 100644 index e85c37ca2..000000000 --- a/tests/repl-targets/hs_bin_repl_test.go +++ /dev/null @@ -1,106 +0,0 @@ -package hs_bin_repl_test - -import ( - bt "github.com/bazelbuild/rules_go/go/tools/bazel_testing" - it "github.com/tweag/rules_haskell/tests/integration_testing" - "testing" -) - -var testcase = ` --- WORKSPACE -- -local_repository( - name = "rules_haskell", - path = "../rules_haskell", -) -load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") -load("@rules_haskell//haskell:repositories.bzl", "rules_haskell_dependencies") - -rules_haskell_dependencies() - -load("@rules_haskell//haskell:nixpkgs.bzl", "haskell_register_ghc_nixpkgs") - -haskell_register_ghc_nixpkgs( - attribute_path = "haskell.compiler.ghc8107", - repository = "@rules_haskell//nixpkgs:default.nix", - version = "8.10.7", -) - -load("@rules_haskell//haskell:toolchain.bzl", "rules_haskell_toolchains") - -rules_haskell_toolchains(version = "8.10.7") - -load( - "@io_tweag_rules_nixpkgs//nixpkgs:nixpkgs.bzl", - "nixpkgs_cc_configure", - "nixpkgs_python_configure", -) - -nixpkgs_cc_configure( - name = "nixpkgs_config_cc", - repository = "@rules_haskell//nixpkgs:default.nix", -) - -nixpkgs_python_configure( - repository = "@rules_haskell//nixpkgs:default.nix", -) - --- BUILD.bazel -- -load( - "@rules_haskell//haskell:defs.bzl", - "haskell_library", - "haskell_test", -) - -load( - "@rules_haskell//haskell:defs.bzl", - "haskell_toolchain_library", -) - -[ - haskell_toolchain_library(name = name) - for name in [ - "base", - ] -] - -haskell_library( - name = "QuuxLib", - srcs = ["QuuxLib.hs"], - deps = [":base"], -) - -haskell_test( - name = "hs-bin", - srcs = ["Quux.hs"], - visibility = ["//visibility:public"], - deps = [ - ":QuuxLib", - ":base", - ], -) - --- Quux.hs -- -module Main (main) where - -import QuuxLib (message) - -main :: IO () -main = putStrLn message --- QuuxLib.hs -- -module QuuxLib (message) where - -message :: String -message = "Hello GHCi!" -` - -func TestMain(m *testing.M) { - it.TestMain(m, bt.Args{Main: testcase}) -} - -func TestHsBinRepl(t *testing.T) { - out, err := it.BazelOutput(it.Context.BazelBinary, "run", "//:hs-bin@repl", "--", "-ignore-dot-ghci", "-e", ":main") - if err != nil { - t.Fatal(err) - } - it.AssertOutput(t, out, "Hello GHCi!\n") -} diff --git a/tests/repl-targets/hs_bin_repl_test/BUILD.bazel b/tests/repl-targets/hs_bin_repl_test/BUILD.bazel new file mode 100644 index 000000000..2c6a09203 --- /dev/null +++ b/tests/repl-targets/hs_bin_repl_test/BUILD.bazel @@ -0,0 +1,32 @@ +load( + "@rules_haskell//haskell:defs.bzl", + "haskell_library", + "haskell_test", +) +load( + "@rules_haskell//haskell:defs.bzl", + "haskell_toolchain_library", +) + +[ + haskell_toolchain_library(name = name) + for name in [ + "base", + ] +] + +haskell_library( + name = "QuuxLib", + srcs = ["QuuxLib.hs"], + deps = [":base"], +) + +haskell_test( + name = "hs-bin", + srcs = ["Quux.hs"], + visibility = ["//visibility:public"], + deps = [ + ":QuuxLib", + ":base", + ], +) diff --git a/tests/repl-targets/hs_bin_repl_test/Quux.hs b/tests/repl-targets/hs_bin_repl_test/Quux.hs new file mode 100644 index 000000000..2e67d221f --- /dev/null +++ b/tests/repl-targets/hs_bin_repl_test/Quux.hs @@ -0,0 +1,6 @@ +module Main (main) where + +import QuuxLib (message) + +main :: IO () +main = putStrLn message diff --git a/tests/repl-targets/hs_bin_repl_test/QuuxLib.hs b/tests/repl-targets/hs_bin_repl_test/QuuxLib.hs new file mode 100644 index 000000000..d321de2ad --- /dev/null +++ b/tests/repl-targets/hs_bin_repl_test/QuuxLib.hs @@ -0,0 +1,4 @@ +module QuuxLib (message) where + +message :: String +message = "Hello GHCi!" diff --git a/tests/repl-targets/hs_bin_repl_test/WORKSPACE b/tests/repl-targets/hs_bin_repl_test/WORKSPACE new file mode 100644 index 000000000..43aaf02d8 --- /dev/null +++ b/tests/repl-targets/hs_bin_repl_test/WORKSPACE @@ -0,0 +1,36 @@ +local_repository( + name = "rules_haskell", + path = "%RULES_HASKELL_PATH%", +) + +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") +load("@rules_haskell//haskell:repositories.bzl", "rules_haskell_dependencies") + +rules_haskell_dependencies() + +load("@rules_haskell//haskell:nixpkgs.bzl", "haskell_register_ghc_nixpkgs") + +haskell_register_ghc_nixpkgs( + attribute_path = "haskell.compiler.ghc8107", + repository = "@rules_haskell//nixpkgs:default.nix", + version = "8.10.7", +) + +load("@rules_haskell//haskell:toolchain.bzl", "rules_haskell_toolchains") + +rules_haskell_toolchains(version = "8.10.7") + +load( + "@io_tweag_rules_nixpkgs//nixpkgs:nixpkgs.bzl", + "nixpkgs_cc_configure", + "nixpkgs_python_configure", +) + +nixpkgs_cc_configure( + name = "nixpkgs_config_cc", + repository = "@rules_haskell//nixpkgs:default.nix", +) + +nixpkgs_python_configure( + repository = "@rules_haskell//nixpkgs:default.nix", +) diff --git a/tests/repl-targets/hs_lib_repl_test/BUILD.bazel b/tests/repl-targets/hs_lib_repl_test/BUILD.bazel new file mode 100644 index 000000000..98113be57 --- /dev/null +++ b/tests/repl-targets/hs_lib_repl_test/BUILD.bazel @@ -0,0 +1,82 @@ +load("@rules_haskell//haskell:c2hs.bzl", "c2hs_library", "c2hs_toolchain") +load( + "@rules_haskell//haskell:defs.bzl", + "haskell_library", + "haskell_test", +) +load( + "@rules_haskell//haskell:defs.bzl", + "haskell_toolchain_library", +) + +[ + haskell_toolchain_library(name = name) + for name in [ + "array", + "base", + ] +] + +genrule( + name = "codegen", + outs = [ + "Gen.hs", + ], + cmd = """ + echo "module Gen (gen) where" >> $(location :Gen.hs) + echo "gen :: String" >> $(location :Gen.hs) + echo "gen = \\"gen\\"" >> $(location :Gen.hs) +""", +) + +c2hs_toolchain( + name = "c2hs-toolchain", + c2hs = "@stackage-exe//c2hs", +) + +c2hs_library( + name = "chs", + srcs = ["Chs.chs"], +) + +cc_library( + name = "ourclibrary", + srcs = [":ourclibrary.c"], + linkstatic = False, + visibility = ["//visibility:public"], +) + +config_setting( + name = "nix", + constraint_values = [ + "@io_tweag_rules_nixpkgs//nixpkgs/constraints:support_nix", + ], +) + +alias( + name = "zlib", + # This is a dependency to @stackage-zlib. + testonly = 0, + actual = select({ + ":nix": "@zlib.dev//:zlib", + "//conditions:default": "@zlib.hs//:zlib", + }), + visibility = ["//visibility:public"], +) + +haskell_library( + name = "hs-lib", + srcs = [ + "Foo.hs", + "Hsc.hsc", + ":chs", + ":codegen", + ], + visibility = ["//visibility:public"], + deps = [ + ":array", + ":base", + ":ourclibrary", + ":zlib", + ], +) diff --git a/tests/repl-targets/hs_lib_repl_test/Chs.chs b/tests/repl-targets/hs_lib_repl_test/Chs.chs new file mode 100644 index 000000000..c66bae7d6 --- /dev/null +++ b/tests/repl-targets/hs_lib_repl_test/Chs.chs @@ -0,0 +1,6 @@ +module Chs + ( baz ) +where + +baz :: String +baz = "baz" diff --git a/tests/repl-targets/hs_lib_repl_test/Foo.hs b/tests/repl-targets/hs_lib_repl_test/Foo.hs new file mode 100644 index 000000000..5e5138418 --- /dev/null +++ b/tests/repl-targets/hs_lib_repl_test/Foo.hs @@ -0,0 +1,9 @@ +{-# LANGUAGE ForeignFunctionInterface #-} + +module Foo (foo) where + +foreign import ccall "c_add_one" + c_add_one :: Int -> Int + +foo :: Int -> Int +foo = (+ 5) . c_add_one diff --git a/tests/repl-targets/hs_lib_repl_test/Hsc.hs b/tests/repl-targets/hs_lib_repl_test/Hsc.hs new file mode 100644 index 000000000..d4e3d6605 --- /dev/null +++ b/tests/repl-targets/hs_lib_repl_test/Hsc.hs @@ -0,0 +1,8 @@ +module Hsc + ( bar ) +where + +#ifndef _INTERNAL_HSC_DO_NOT_DEFINE_ME +bar :: String +bar = "bar" +#endif diff --git a/tests/repl-targets/hs_lib_repl_test/Hsc.hsc b/tests/repl-targets/hs_lib_repl_test/Hsc.hsc new file mode 100644 index 000000000..d4e3d6605 --- /dev/null +++ b/tests/repl-targets/hs_lib_repl_test/Hsc.hsc @@ -0,0 +1,8 @@ +module Hsc + ( bar ) +where + +#ifndef _INTERNAL_HSC_DO_NOT_DEFINE_ME +bar :: String +bar = "bar" +#endif diff --git a/tests/repl-targets/hs_lib_repl_test.go b/tests/repl-targets/hs_lib_repl_test/WORKSPACE similarity index 58% rename from tests/repl-targets/hs_lib_repl_test.go rename to tests/repl-targets/hs_lib_repl_test/WORKSPACE index ffe8b6e16..865ccc991 100644 --- a/tests/repl-targets/hs_lib_repl_test.go +++ b/tests/repl-targets/hs_lib_repl_test/WORKSPACE @@ -1,17 +1,8 @@ -package hs_lib_repl_test - -import ( - bt "github.com/bazelbuild/rules_go/go/tools/bazel_testing" - it "github.com/tweag/rules_haskell/tests/integration_testing" - "testing" -) - -var testcase = ` --- WORKSPACE -- load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + local_repository( - name = "rules_haskell", - path = "../rules_haskell", + name = "rules_haskell", + path = "%RULES_HASKELL_PATH%", ) load("@rules_haskell//haskell:repositories.bzl", "rules_haskell_dependencies") @@ -22,6 +13,7 @@ os_info(name = "os_info") load("@os_info//:os_info.bzl", "is_windows") rules_haskell_dependencies() + load("@rules_haskell//haskell:nixpkgs.bzl", "haskell_register_ghc_nixpkgs") haskell_register_ghc_nixpkgs( @@ -37,9 +29,9 @@ rules_haskell_toolchains(version = "8.10.7") load( "@io_tweag_rules_nixpkgs//nixpkgs:nixpkgs.bzl", "nixpkgs_cc_configure", - "nixpkgs_python_configure", "nixpkgs_local_repository", "nixpkgs_package", + "nixpkgs_python_configure", ) nixpkgs_cc_configure( @@ -141,19 +133,19 @@ stack_snapshot( packages = [ "array", "base", - "ghc-heap", "c2hs", + "data-default-class", "doctest", - "polysemy", - "network", - "streaming", - "void", "ghc-check", + "ghc-heap", "hspec", "hspec-core", - "data-default-class", + "network", + "polysemy", "proto-lens-protoc-0.7.0.0", + "streaming", "temporary", + "void", ], stack_snapshot_json = "@rules_haskell//:stackage_snapshot.json" if not is_windows else None, tools = [ @@ -167,142 +159,3 @@ stack_snapshot( register_toolchains( ":c2hs-toolchain", ) --- BUILD.bazel -- -load("@rules_haskell//haskell:c2hs.bzl", "c2hs_library", "c2hs_toolchain") -load( - "@rules_haskell//haskell:defs.bzl", - "haskell_library", - "haskell_test", -) - -load( - "@rules_haskell//haskell:defs.bzl", - "haskell_toolchain_library", -) - -[ - haskell_toolchain_library(name = name) - for name in [ - "array", - "base", - ] -] - -genrule( - name = "codegen", - outs = [ - "Gen.hs", - ], - cmd = """ - echo "module Gen (gen) where" >> $(location :Gen.hs) - echo "gen :: String" >> $(location :Gen.hs) - echo "gen = \\"gen\\"" >> $(location :Gen.hs) -""", -) - -c2hs_toolchain( - name = "c2hs-toolchain", - c2hs = "@stackage-exe//c2hs", -) -c2hs_library( - name = "chs", - srcs = ["Chs.chs"], -) - -cc_library( - name = "ourclibrary", - srcs = [":ourclibrary.c"], - linkstatic = False, - visibility = ["//visibility:public"], -) - -config_setting( - name = "nix", - constraint_values = [ - "@io_tweag_rules_nixpkgs//nixpkgs/constraints:support_nix", - ], -) - -alias( - name = "zlib", - # This is a dependency to @stackage-zlib. - testonly = 0, - actual = select({ - ":nix": "@zlib.dev//:zlib", - "//conditions:default": "@zlib.hs//:zlib", - }), - visibility = ["//visibility:public"], -) - - -haskell_library( - name = "hs-lib", - srcs = [ - "Foo.hs", - "Hsc.hsc", - ":chs", - ":codegen", - ], - visibility = ["//visibility:public"], - deps = [ - ":zlib", - ":ourclibrary", - ":array", - ":base", - ], -) - --- Chs.chs -- -module Chs - ( baz ) -where - -baz :: String -baz = "baz" --- Hsc.hs -- -module Hsc - ( bar ) -where - -#ifndef _INTERNAL_HSC_DO_NOT_DEFINE_ME -bar :: String -bar = "bar" -#endif --- Foo.hs -- -{-# LANGUAGE ForeignFunctionInterface #-} - -module Foo (foo) where - -foreign import ccall "c_add_one" - c_add_one :: Int -> Int - -foo :: Int -> Int -foo = (+ 5) . c_add_one --- ourclibrary.c -- -#include - -int32_t c_add_one(int32_t x) { - return 1 + x; -} --- Hsc.hsc -- -module Hsc - ( bar ) -where - -#ifndef _INTERNAL_HSC_DO_NOT_DEFINE_ME -bar :: String -bar = "bar" -#endif -` - -func TestMain(m *testing.M) { - it.TestMain(m, bt.Args{Main: testcase}) -} - -func TestHsLibRepl(t *testing.T) { - out, err := it.BazelOutput(it.Context.BazelBinary, "run", "//:hs-lib@repl", "--", "-ignore-dot-ghci", "-e", "show (foo 10) ++ bar ++ baz ++ gen") - if err != nil { - t.Fatal(err) - } - it.AssertOutput(t, out, "\"16barbazgen\"\n") -} diff --git a/tests/repl-targets/hs_lib_repl_test/ourclibrary.c b/tests/repl-targets/hs_lib_repl_test/ourclibrary.c new file mode 100644 index 000000000..587d26ba2 --- /dev/null +++ b/tests/repl-targets/hs_lib_repl_test/ourclibrary.c @@ -0,0 +1,5 @@ +#include + +int32_t c_add_one(int32_t x) { + return 1 + x; +} diff --git a/tests/stack-snapshot-deps/BUILD.bazel b/tests/stack-snapshot-deps/BUILD.bazel index 8f62a0faf..decdc0a7e 100644 --- a/tests/stack-snapshot-deps/BUILD.bazel +++ b/tests/stack-snapshot-deps/BUILD.bazel @@ -3,7 +3,7 @@ load( "haskell_test", ) load("@io_bazel_rules_go//go/tools/bazel_testing:def.bzl", "go_bazel_test") -load("//tests:integration_tests.bzl", "integration_test") +load("//tests/integration_testing:rules_haskell_integration_test.bzl", "rules_haskell_integration_test") package(default_testonly = 1) @@ -24,9 +24,10 @@ haskell_test( ], ) -integration_test( +rules_haskell_integration_test( name = "hs_override_stack_test", - bazel = "//tests:bazel", + srcs = ["HsOverrideStackTest.hs"], + workspace_path = "hs_override_stack_test", ) filegroup( diff --git a/tests/stack-snapshot-deps/HsOverrideStackTest.hs b/tests/stack-snapshot-deps/HsOverrideStackTest.hs new file mode 100644 index 000000000..56e8d8e0b --- /dev/null +++ b/tests/stack-snapshot-deps/HsOverrideStackTest.hs @@ -0,0 +1,13 @@ +{-# OPTIONS -Wall #-} + +import Data.List (isInfixOf) +import IntegrationTesting +import Test.Hspec (hspec, it) + +main :: IO () +main = hspec $ do + it "bazel run repl" $ do + bazel <- setupTestBazel + let p (_stdout, stderr) = "parsing JSON failed" `isInfixOf` stderr + in + failedOutputSatisfy p (bazel ["run", "//:hs-bin@repl", "--", "-ignore-dot-ghci", "-e", ":main"]) diff --git a/tests/stack-snapshot-deps/hs_override_stack_test.go b/tests/stack-snapshot-deps/hs_override_stack_test.go deleted file mode 100644 index 8b9a1fc82..000000000 --- a/tests/stack-snapshot-deps/hs_override_stack_test.go +++ /dev/null @@ -1,121 +0,0 @@ -package hs_override_stack_test - -import ( - bt "github.com/bazelbuild/rules_go/go/tools/bazel_testing" - it "github.com/tweag/rules_haskell/tests/integration_testing" - "os" - "path" - "strings" - "testing" -) - -var testcase = ` --- WORKSPACE -- -local_repository( - name = "rules_haskell", - path = "../rules_haskell", -) - -load("@rules_haskell//haskell:repositories.bzl", "rules_haskell_dependencies") - -rules_haskell_dependencies() - -load("@rules_haskell//haskell:nixpkgs.bzl", "haskell_register_ghc_nixpkgs") - -haskell_register_ghc_nixpkgs( - attribute_path = "haskell.compiler.ghc8107", - repository = "@rules_haskell//nixpkgs:default.nix", - version = "8.10.7", -) - -load("@rules_haskell//haskell:toolchain.bzl", "rules_haskell_toolchains") - -rules_haskell_toolchains(version = "8.10.7") - -load( - "@io_tweag_rules_nixpkgs//nixpkgs:nixpkgs.bzl", - "nixpkgs_cc_configure", - "nixpkgs_python_configure", -) - -nixpkgs_cc_configure( - name = "nixpkgs_config_cc", - repository = "@rules_haskell//nixpkgs:default.nix", -) - -nixpkgs_python_configure( - repository = "@rules_haskell//nixpkgs:default.nix", -) - -load( - "@rules_haskell//haskell:cabal.bzl", - "stack_snapshot", - "use_stack", -) - -# use dummy stack that only passes version check. -# this should override the default behavior of 'stack_snapshot' -# to use whatever is available in the environment. -use_stack("//:stack") - -stack_snapshot( - name = "stackage", - local_snapshot = "@rules_haskell//:stackage_snapshot.yaml", - packages = [ - "base", - ], -) - --- stack -- -#!/bin/sh -echo 2.3.1 - --- BUILD.bazel -- -load( - "@rules_haskell//haskell:defs.bzl", - "haskell_test", -) - -haskell_test( - name = "hs-bin", - srcs = ["Quux.hs"], - visibility = ["//visibility:public"], - deps = [ "@stackage//:base" ], -) - --- Quux.hs -- -module Main (main) where - -main :: IO () -main = putStrLn "Hello GHCi!" -` - -func TestMain(m *testing.M) { - it.TestMain(m, bt.Args{ - Main: testcase, - SetUp: func() (err error) { - // set executable bit on dummy script, since `bazel_testing` does not allow it - // https://github.com/bazelbuild/rules_go/issues/2281 - dir, err := os.Getwd() - if err != nil { - return - } - stack := path.Join(dir, "stack") - info, err := os.Stat(stack) - if err != nil { - return - } - return os.Chmod(stack, 0700|info.Mode()) - }, - }) -} - -func TestHsBinRepl(t *testing.T) { - _, err := it.BazelOutput(it.Context.BazelBinary, "run", "//:hs-bin@repl", "--", "-ignore-dot-ghci", "-e", ":main") - if err == nil { - t.Fatal(err, "build succeeds, but should fail due invalid `stack` binary") - } - if !strings.Contains(err.Error(), "parsing JSON failed") { - t.Fatal(err, "build does not use specified dummy `stack`") - } -} diff --git a/tests/stack-snapshot-deps/hs_override_stack_test/BUILD.bazel b/tests/stack-snapshot-deps/hs_override_stack_test/BUILD.bazel new file mode 100644 index 000000000..2834c15bc --- /dev/null +++ b/tests/stack-snapshot-deps/hs_override_stack_test/BUILD.bazel @@ -0,0 +1,11 @@ +load( + "@rules_haskell//haskell:defs.bzl", + "haskell_test", +) + +haskell_test( + name = "hs-bin", + srcs = ["Quux.hs"], + visibility = ["//visibility:public"], + deps = ["@stackage//:base"], +) diff --git a/tests/stack-snapshot-deps/hs_override_stack_test/Quux.hs b/tests/stack-snapshot-deps/hs_override_stack_test/Quux.hs new file mode 100644 index 000000000..63f2fb2e2 --- /dev/null +++ b/tests/stack-snapshot-deps/hs_override_stack_test/Quux.hs @@ -0,0 +1,4 @@ +module Main (main) where + +main :: IO () +main = putStrLn "Hello GHCi!" diff --git a/tests/stack-snapshot-deps/hs_override_stack_test/WORKSPACE b/tests/stack-snapshot-deps/hs_override_stack_test/WORKSPACE new file mode 100644 index 000000000..4d45a895c --- /dev/null +++ b/tests/stack-snapshot-deps/hs_override_stack_test/WORKSPACE @@ -0,0 +1,54 @@ +local_repository( + name = "rules_haskell", + path = "%RULES_HASKELL_PATH%", +) + +load("@rules_haskell//haskell:repositories.bzl", "rules_haskell_dependencies") + +rules_haskell_dependencies() + +load("@rules_haskell//haskell:nixpkgs.bzl", "haskell_register_ghc_nixpkgs") + +haskell_register_ghc_nixpkgs( + attribute_path = "haskell.compiler.ghc8107", + repository = "@rules_haskell//nixpkgs:default.nix", + version = "8.10.7", +) + +load("@rules_haskell//haskell:toolchain.bzl", "rules_haskell_toolchains") + +rules_haskell_toolchains(version = "8.10.7") + +load( + "@io_tweag_rules_nixpkgs//nixpkgs:nixpkgs.bzl", + "nixpkgs_cc_configure", + "nixpkgs_python_configure", +) + +nixpkgs_cc_configure( + name = "nixpkgs_config_cc", + repository = "@rules_haskell//nixpkgs:default.nix", +) + +nixpkgs_python_configure( + repository = "@rules_haskell//nixpkgs:default.nix", +) + +load( + "@rules_haskell//haskell:cabal.bzl", + "stack_snapshot", + "use_stack", +) + +# use dummy stack that only passes version check. +# this should override the default behavior of 'stack_snapshot' +# to use whatever is available in the environment. +use_stack("//:stack") + +stack_snapshot( + name = "stackage", + local_snapshot = "@rules_haskell//:stackage_snapshot.yaml", + packages = [ + "base", + ], +) diff --git a/tests/stack-snapshot-deps/hs_override_stack_test/stack b/tests/stack-snapshot-deps/hs_override_stack_test/stack new file mode 100755 index 000000000..88c09f659 --- /dev/null +++ b/tests/stack-snapshot-deps/hs_override_stack_test/stack @@ -0,0 +1,2 @@ +#!/bin/sh +echo 2.3.1 diff --git a/tools/repositories.bzl b/tools/repositories.bzl index d569308e6..999c7e1ba 100644 --- a/tools/repositories.bzl +++ b/tools/repositories.bzl @@ -2,10 +2,10 @@ load("@rules_haskell//haskell:cabal.bzl", "stack_snapshot") load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_file") -load("@io_tweag_rules_nixpkgs//nixpkgs:nixpkgs.bzl", "nixpkgs_package") def rules_haskell_worker_dependencies(**stack_kwargs): - """Provide all repositories that are necessary for `rules_haskell`'s tools to + """ + Provide all repositories that are necessary for `rules_haskell`'s tools to function. """ excludes = native.existing_rules().keys() @@ -30,37 +30,3 @@ def rules_haskell_worker_dependencies(**stack_kwargs): snapshot = "lts-18.0", **stack_kwargs ) - -def bazel_binaries_for_integration_testing(): - http_file( - name = "bazel_bin_linux", - executable = True, - sha256 = "0eb2e378d2782e7810753e2162245ad1179c1bb12f848c692b4a595b4edf779b", - urls = ["https://github.com/bazelbuild/bazel/releases/download/4.1.0/bazel-4.1.0-linux-x86_64"], - ) - - http_file( - name = "bazel_bin_darwin", - executable = True, - sha256 = "2eecc3abb0ff653ed0bffdb9fbfda7b08548c2868f13da4a995f01528db200a9", - urls = ["https://github.com/bazelbuild/bazel/releases/download/4.1.0/bazel-4.1.0-darwin-x86_64"], - ) - - http_file( - name = "bazel_bin_windows", - executable = True, - sha256 = "7b2077af7055b421fe31822f83c3c3c15e36ff39b69560ba2472dde92dd45b46", - urls = ["https://github.com/bazelbuild/bazel/releases/download/4.1.0/bazel-4.1.0-windows-x86_64.exe"], - ) - - nixpkgs_package( - name = "bazel_4", - repository = "@nixpkgs_default", - build_file_content = """\ -filegroup( - name = "bazel_bin", - srcs = ["bin/bazel"], - visibility = [ "//visibility:public" ], -) -""", - )