All Downloads are FREE. Search and download functionalities are using the official Maven repository.

coursier.clitests.BootstrapTests.scala Maven / Gradle / Ivy

There is a newer version: 2.1.13
Show newest version
package coursier.clitests

import java.io._
import java.net.{ServerSocket, URI}
import java.nio.charset.Charset
import java.nio.file.Files
import java.util.{Locale, UUID}
import java.util.regex.Pattern
import java.util.zip.ZipFile

import scala.collection.JavaConverters._
import scala.concurrent.duration.Duration
import scala.io.{Codec, Source}
import scala.util.Properties

import coursier.clitests.util.TestAuthProxy
import coursier.util.StringInterpolators._
import utest._

abstract class BootstrapTests extends TestSuite with LauncherOptions {

  def launcher: String
  def assembly: String

  def overrideProguarded: Option[Boolean] =
    None

  def enableNailgunTest: Boolean =
    true

  def hasDocker: Boolean =
    Properties.isLinux

  private val extraOptions =
    overrideProguarded match {
      case None        => Nil
      case Some(value) => Seq(s"--proguarded=$value")
    }

  val tests = Tests {
    test("simple") {
      TestUtil.withTempDir { tmpDir0 =>
        val tmpDir = os.Path(tmpDir0)
        os.proc(
          launcher,
          "bootstrap",
          "-o",
          "cs-echo",
          "io.get-coursier:echo:1.0.1",
          extraOptions
        ).call(cwd = tmpDir)
        val bootstrap =
          if (Properties.isWin) (tmpDir / "cs-echo.bat").toString
          else "./cs-echo"
        val output = os.proc(bootstrap, "foo")
          .call(cwd = tmpDir)
          .out.text()
        val expectedOutput = "foo" + System.lineSeparator()
        assert(output == expectedOutput)

        if (acceptsJOptions) {
          val outputWithJavaArgs =
            os.proc(bootstrap, "-J-Dother=thing", "foo", "-J-Dfoo=baz")
              .call(cwd = tmpDir)
              .out.text()
          assert(outputWithJavaArgs == expectedOutput)
        }

        val outputWithArgsWithSpace =
          os.proc(bootstrap, "-n foo")
            .call(cwd = tmpDir)
            .out.text()
        val expectedOutputWithArgsWithSpace = "-n foo" + System.lineSeparator()
        assert(outputWithArgsWithSpace == expectedOutputWithArgsWithSpace)
      }
    }

    def javaPropsTest(): Unit =
      TestUtil.withTempDir { tmpDir =>
        os.proc(
          launcher,
          "bootstrap",
          "-o",
          "cs-props",
          "--property",
          "other=thing",
          "--java-opt",
          "-Dfoo=baz",
          TestUtil.propsDepStr,
          "--jvm-option-file=.propsjvmopts",
          extraOptions
        ).call(cwd = os.Path(tmpDir))

        val fooOutput =
          os.proc("./cs-props", "foo")
            .call(cwd = os.Path(tmpDir))
            .out.text()
        val expectedFooOutput = "baz" + System.lineSeparator()
        assert(fooOutput == expectedFooOutput)

        val otherOutput = LauncherTestUtil.output(
          Seq("./cs-props", "other"),
          keepErrorOutput = false,
          directory = tmpDir
        )
        val expectedOtherOutput = "thing" + System.lineSeparator()
        assert(otherOutput == expectedOtherOutput)

        if (acceptsJOptions) {
          val propArgOutput = LauncherTestUtil.output(
            Seq("./cs-props", "-J-Dhappy=days", "happy"),
            keepErrorOutput = false,
            directory = tmpDir
          )
          val expectedPropArgOutput = "days" + System.lineSeparator()
          assert(propArgOutput == expectedPropArgOutput)

          val optFile = new File(tmpDir, ".propsjvmopts")
          Files.write(
            optFile.toPath,
            ("-Dhappy=days" + System.lineSeparator()).getBytes(Charset.defaultCharset())
          )
          val optFileOutput = LauncherTestUtil.output(
            Seq("./cs-props", "happy"),
            keepErrorOutput = false,
            directory = tmpDir
          )
          Files.delete(optFile.toPath)
          val expectedOptFileOutput = "days" + System.lineSeparator()
          assert(optFileOutput == expectedOptFileOutput)
        }

        val javaOptsOutput = LauncherTestUtil.output(
          Seq("./cs-props", "happy"),
          keepErrorOutput = false,
          directory = tmpDir,
          extraEnv = Map("JAVA_OPTS" -> "-Dhappy=days")
        )
        val expectedJavaOptsOutput = "days" + System.lineSeparator()
        assert(javaOptsOutput == expectedJavaOptsOutput)

        val multiJavaOptsOutput = LauncherTestUtil.output(
          Seq("./cs-props", "happy"),
          keepErrorOutput = false,
          directory = tmpDir,
          extraEnv = Map("JAVA_OPTS" -> "-Dhappy=days -Dfoo=other")
        )
        val expectedMultiJavaOptsOutput = "days" + System.lineSeparator()
        assert(multiJavaOptsOutput == expectedMultiJavaOptsOutput)
      }
    test("java props") {
      if (acceptsDOptions) {
        javaPropsTest()
        "ok"
      }
      else
        "disabled"
    }

    def javaPropsWithAssemblyTest(): Unit =
      TestUtil.withTempDir { tmpDir =>
        LauncherTestUtil.run(
          args = Seq(
            launcher,
            "bootstrap",
            "-a",
            "-o",
            "cs-props-assembly",
            "--property",
            "other=thing",
            "--java-opt",
            "-Dfoo=baz",
            TestUtil.propsDepStr
          ) ++ extraOptions,
          directory = tmpDir
        )

        val fooOutput = LauncherTestUtil.output(
          Seq("./cs-props-assembly", "foo"),
          keepErrorOutput = false,
          directory = tmpDir
        )
        val expectedFooOutput = "baz" + System.lineSeparator()
        assert(fooOutput == expectedFooOutput)

        val otherOutput = LauncherTestUtil.output(
          Seq("./cs-props-assembly", "other"),
          keepErrorOutput = false,
          directory = tmpDir
        )
        val expectedOtherOutput = "thing" + System.lineSeparator()
        assert(otherOutput == expectedOtherOutput)

        // FIXME Test more stuff like cs-props above?
      }
    test("java props via assembly") {
      if (acceptsDOptions) {
        javaPropsWithAssemblyTest()
        "ok"
      }
      else
        "disabled"
    }

    test("java class path property") {
      TestUtil.withTempDir { tmpDir =>
        LauncherTestUtil.run(
          args =
            Seq(launcher, "bootstrap", "-o", "cs-props-0", TestUtil.propsDepStr) ++ extraOptions,
          directory = tmpDir
        )
        val output = LauncherTestUtil.output(
          Seq("./cs-props-0", "java.class.path"),
          keepErrorOutput = false,
          directory = tmpDir
        )
        if (Properties.isWin) {
          val expectedOutput =
            (new File(tmpDir, "./cs-props-0").getCanonicalPath +: TestUtil.propsCp).mkString(
              File.pathSeparator
            ) + System.lineSeparator()
          assert(output.replace("\\\\", "\\") == expectedOutput)
        }
        else {
          val expectedOutput = ("./cs-props-0" +: TestUtil.propsCp).mkString(
            File.pathSeparator
          ) + System.lineSeparator()
          assert(output == expectedOutput)
        }
      }
    }

    test("java_class_path property in expansion") {
      TestUtil.withTempDir { tmpDir =>
        val pwd     = if (Properties.isWin) os.Path(os.pwd.toIO.getCanonicalFile) else os.pwd
        val tmpDir0 = os.Path(if (Properties.isWin) tmpDir.getCanonicalFile else tmpDir, pwd)
        os.proc(
          LauncherTestUtil.adaptCommandName(launcher, tmpDir),
          "bootstrap",
          "-o",
          "cs-props-1",
          "--property",
          "foo=${java.class.path}__${java.class.path}",
          TestUtil.propsDepStr,
          extraOptions
        ).call(cwd = tmpDir0)
        val output = os.proc(LauncherTestUtil.adaptCommandName("./cs-props-1", tmpDir), "foo")
          .call(cwd = tmpDir0)
          .out.text()
        if (Properties.isWin) {
          val outputElems =
            ((tmpDir0 / "cs-props-1").toString +: TestUtil.propsCp).mkString(File.pathSeparator)
          val expectedOutput = outputElems + "__" + outputElems + System.lineSeparator()
          assert(output.replace("\\\\", "\\") == expectedOutput)
        }
        else {
          val cp             = ("./cs-props-1" +: TestUtil.propsCp).mkString(File.pathSeparator)
          val expectedOutput = cp + "__" + cp + System.lineSeparator()
          assert(output == expectedOutput)
        }
      }
    }

    test("space in main jar path") {
      TestUtil.withTempDir { tmpDir =>
        Files.createDirectories(tmpDir.toPath.resolve("dir with space"))
        LauncherTestUtil.run(
          args = Seq(
            launcher,
            "bootstrap",
            "-o",
            "dir with space/cs-props-0",
            TestUtil.propsDepStr
          ) ++ extraOptions,
          directory = tmpDir
        )
        val output = LauncherTestUtil.output(
          Seq("./dir with space/cs-props-0", "coursier.mainJar"),
          keepErrorOutput = false,
          directory = tmpDir
        )
        val expectedInOutput = "dir with space"
        assert(output.contains(expectedInOutput))
      }
    }

    test("manifest jar") {
      TestUtil.withTempDir { tmpDir =>
        LauncherTestUtil.run(
          args = Seq(
            launcher,
            "bootstrap",
            "-o",
            "cs-echo-mf",
            "io.get-coursier:echo:1.0.1",
            "--manifest-jar"
          ) ++ extraOptions,
          directory = tmpDir
        )
        val output = LauncherTestUtil.output(
          Seq("./cs-echo-mf", "foo"),
          keepErrorOutput = false,
          directory = tmpDir
        )
        val expectedOutput = "foo" + System.lineSeparator()
        assert(output == expectedOutput)
      }
    }

    test("hybrid") {
      TestUtil.withTempDir { tmpDir =>
        LauncherTestUtil.run(
          args = Seq(
            launcher,
            "bootstrap",
            "-o",
            "cs-echo-hybrid",
            "io.get-coursier:echo:1.0.1",
            "--hybrid"
          ) ++ extraOptions,
          directory = tmpDir
        )
        val output = LauncherTestUtil.output(
          Seq("./cs-echo-hybrid", "foo"),
          keepErrorOutput = false,
          directory = tmpDir
        )
        val expectedOutput = "foo" + System.lineSeparator()
        assert(output == expectedOutput)
      }
    }

    test("hybrid java.class.path") {
      TestUtil.withTempDir { tmpDir =>
        LauncherTestUtil.run(
          args = Seq(
            launcher,
            "bootstrap",
            "-o",
            "cs-props-hybrid",
            TestUtil.propsDepStr,
            "--hybrid"
          ) ++ extraOptions,
          directory = tmpDir
        )
        val output = LauncherTestUtil.output(
          Seq("./cs-props-hybrid", "java.class.path"),
          keepErrorOutput = false,
          directory = tmpDir
        )
        if (Properties.isWin) {
          val expectedOutput =
            new File(tmpDir, "./cs-props-hybrid").getCanonicalPath + System.lineSeparator()
          assert(output.replace("\\\\", "\\") == expectedOutput)
        }
        else {
          val expectedOutput = "./cs-props-hybrid" + System.lineSeparator()
          assert(output == expectedOutput)
        }
      }
    }

    test("hybrid with shared dep java class path") {
      TestUtil.withTempDir { tmpDir =>
        val tmpDir0 = os.Path(tmpDir, os.pwd)
        os.proc(
          LauncherTestUtil.adaptCommandName(launcher, tmpDir),
          "bootstrap",
          "-o",
          "cs-props-hybrid-shared",
          TestUtil.propsDepStr,
          "io.get-coursier:echo:1.0.2",
          "--shared",
          "io.get-coursier:echo",
          "--hybrid",
          extraOptions
        ).call(cwd = tmpDir0)

        val zf = new ZipFile((tmpDir0 / "cs-props-hybrid-shared").toIO)
        val nativeImageEntries = zf.entries()
          .asScala
          .filter(_.getName.startsWith("META-INF/native-image/"))
          .toVector
        zf.close()
        assert(nativeImageEntries.isEmpty)

        val output = os.proc(
          LauncherTestUtil.adaptCommandName("./cs-props-hybrid-shared", tmpDir),
          "java.class.path"
        )
          .call(cwd = tmpDir0)
          .out.text(Codec.default)
        if (Properties.isWin) {
          val expectedOutput =
            new File(tmpDir, "./cs-props-hybrid-shared").getCanonicalPath + System.lineSeparator()
          assert(output.replace("\\\\", "\\") == expectedOutput)
        }
        else {
          val expectedOutput = "./cs-props-hybrid-shared" + System.lineSeparator()
          assert(output == expectedOutput)
        }
      }
    }

    test("hybrid with rules") {
      TestUtil.withTempDir { tmpDir =>
        val tmpDir0 = os.Path(tmpDir, os.pwd)

        def generate(output: String, extraArgs: String*): Unit =
          os.proc(
            LauncherTestUtil.adaptCommandName(launcher, tmpDir),
            "bootstrap",
            "-o",
            output,
            TestUtil.propsDepStr,
            "io.get-coursier:echo:1.0.2",
            "--shared",
            "io.get-coursier:echo",
            "--hybrid",
            extraArgs,
            extraOptions
          ).call(cwd = tmpDir0)

        generate("base")
        generate("with-rule", "-R", "exclude:coursier/echo/Echo.class")

        def hasEchoEntry(output: String): Boolean = {
          val zf = new ZipFile((tmpDir0 / output).toIO)
          try
            zf.entries()
              .asScala
              .exists(_.getName == "coursier/echo/Echo.class")
          finally
            zf.close()
        }

        assert(hasEchoEntry("base"))
        assert(!hasEchoEntry("with-rule"))
      }
    }

    test("standalone") {
      TestUtil.withTempDir { tmpDir =>
        LauncherTestUtil.run(
          args = Seq(
            launcher,
            "bootstrap",
            "-o",
            "cs-echo-standalone",
            "io.get-coursier:echo:1.0.1",
            "--standalone"
          ) ++ extraOptions,
          directory = tmpDir
        )
        val output = LauncherTestUtil.output(
          Seq("./cs-echo-standalone", "foo"),
          keepErrorOutput = false,
          directory = tmpDir
        )
        val expectedOutput = "foo" + System.lineSeparator()
        assert(output == expectedOutput)
      }
    }

    test("scalafmt standalone") {
      TestUtil.withTempDir { tmpDir =>
        LauncherTestUtil.run(
          args = Seq(
            launcher,
            "bootstrap",
            "-o",
            "cs-scalafmt-standalone",
            "org.scalameta:scalafmt-cli_2.12:2.0.0-RC4",
            "--standalone"
          ) ++ extraOptions,
          directory = tmpDir
        )
        LauncherTestUtil.run(
          Seq("./cs-scalafmt-standalone", "--help"),
          directory = tmpDir
        )
      }
    }

    test("jar with bash preamble") {
      // source jar here has a bash preamble, which assembly should ignore
      TestUtil.withTempDir { tmpDir =>
        LauncherTestUtil.run(
          args = Seq(
            launcher,
            "bootstrap",
            "--intransitive",
            "io.get-coursier::coursier-cli:1.1.0-M14-2",
            "-o",
            "coursier-test.jar",
            "--assembly",
            "--classifier",
            "standalone",
            "-A",
            "jar"
          ) ++ extraOptions,
          directory = tmpDir
        )
        LauncherTestUtil.run(
          Seq("./coursier-test.jar", "--help"),
          directory = tmpDir
        )
      }
    }

    test("nailgun") {
      if (enableNailgunTest)
        TestUtil.withTempDir { tmpDir =>
          LauncherTestUtil.run(
            args = Seq(
              launcher,
              "bootstrap",
              "-o",
              "echo-ng",
              "--standalone",
              "io.get-coursier:echo:1.0.0",
              "com.facebook:nailgun-server:1.0.0",
              "-M",
              "com.facebook.nailgun.NGServer"
            ) ++ extraOptions,
            directory = tmpDir
          )
          var bgProc: Process = null
          val output =
            try {
              bgProc = new ProcessBuilder("java", "-jar", "./echo-ng")
                .directory(tmpDir)
                .inheritIO()
                .start()

              Thread.sleep(2000L)

              LauncherTestUtil.output(
                Seq(TestUtil.ngCommand, "coursier.echo.Echo", "foo"),
                keepErrorOutput = false,
                directory = tmpDir
              )
            }
            finally if (bgProc != null)
                bgProc.destroy()

          val expectedOutput = "foo" + System.lineSeparator()
          assert(output == expectedOutput)
        }
    }

    test("python jep") {
      TestUtil.withTempDir { tmpDir =>
        LauncherTestUtil.run(
          args = Seq(
            launcher,
            "bootstrap",
            "-o",
            "props-python",
            "--python-jep",
            TestUtil.propsDepStr
          ) ++ extraOptions,
          directory = tmpDir
        )

        val jnaLibraryPath = LauncherTestUtil.output(
          Seq("./props-python", "jna.library.path"),
          keepErrorOutput = false,
          directory = tmpDir
        )
        assert(jnaLibraryPath.trim.nonEmpty)

        val jnaNoSys = LauncherTestUtil.output(
          Seq("./props-python", "jna.nosys"),
          keepErrorOutput = false,
          directory = tmpDir
        )
        assert(jnaNoSys.trim == "false")
      }
    }

    test("python") {
      TestUtil.withTempDir { tmpDir0 =>
        val tmpDir = os.Path(tmpDir0)
        os.proc(
          launcher,
          "bootstrap",
          "-o",
          "echo-scalapy",
          "--python",
          "io.get-coursier:scalapy-echo_2.13:1.0.7",
          extraOptions
        ).call(cwd = tmpDir, stdin = os.Inherit, stdout = os.Inherit)
        os.proc(
          launcher,
          "bootstrap",
          "-o",
          "echo-scalapy-no-python",
          "io.get-coursier:scalapy-echo_2.13:1.0.7",
          extraOptions
        ).call(cwd = tmpDir, stdin = os.Inherit, stdout = os.Inherit)

        def bootstrap(name: String): String =
          if (Properties.isWin) (tmpDir / s"$name.bat").toString
          else s"./$name"
        val res = os.proc(bootstrap("echo-scalapy"), "a", "b").call(cwd = tmpDir)
        assert(res.out.trim() == "a b")

        // Commented out, as things can work without Python-specific setup in some
        // environments.
        // val noPythonRes = os.proc(bootstrap("echo-scalapy-no-python"), "a", "b").call(
        //   cwd = tmpDir,
        //   mergeErrIntoOut = true,
        //   check = false
        // )
        // System.err.write(noPythonRes.out.bytes)
        // assert(noPythonRes.exitCode != 0)
      }
    }

    test("python native") {
      // both false means native launcher, where we can't use 'cs bootstrap --native'
      // (as it class loads a launcher-native module at runtime)
      if (acceptsDOptions || acceptsJOptions)
        pythonNativeTest()
    }
    def pythonNativeTest(): Unit = {
      TestUtil.withTempDir { tmpDir =>
        os.proc(
          launcher,
          "bootstrap",
          "-o",
          "echo-scalapy-native",
          "--python",
          "--native",
          "io.get-coursier:scalapy-echo_native0.4_2.13:1.0.7",
          extraOptions
        ).call(cwd = os.Path(tmpDir), stdin = os.Inherit, stdout = os.Inherit)

        val res = os.proc("./echo-scalapy-native", "a", "b").call(cwd = os.Path(tmpDir))
        assert(res.out.trim() == "a b")
      }
    }

    test("authenticated proxy") {
      if (hasDocker) authenticatedProxyTest()
      else "Docker test disabled"
    }

    def authenticatedProxyTest(): Unit = {

      TestUtil.withTempDir { tmpDir0 =>
        val tmpDir = os.Path(tmpDir0, os.pwd)

        os.proc(launcher, "bootstrap", "-o", "cs-echo", "io.get-coursier:echo:1.0.1", extraOptions)
          .call(cwd = tmpDir)

        val output = os.proc("./cs-echo", "foo")
          .call(cwd = tmpDir)
          .out.text()
        val expectedOutput = "foo" + System.lineSeparator()
        assert(output == expectedOutput)

        val okM2Dir = tmpDir / "m2-ok"
        os.write(okM2Dir / "settings.xml", TestAuthProxy.m2Settings(), createFolders = true)
        val nopeM2Dir = tmpDir / "m2-nope"
        os.write(
          nopeM2Dir / "settings.xml",
          TestAuthProxy.m2Settings(9083, "wrong", "nope"),
          createFolders = true
        )

        TestAuthProxy.withAuthProxy { _ =>

          val proc = os.proc("./cs-echo", "foo")

          val failCheck = proc
            .call(
              cwd = tmpDir,
              env = Map(
                "COURSIER_CACHE" -> (tmpDir / "cache-1").toString,
                "CS_MAVEN_HOME"  -> nopeM2Dir.toString
              ),
              check = false,
              mergeErrIntoOut = true
            )

          assert(failCheck.exitCode != 0)
          assert(failCheck.out.text().contains("407 Proxy Authentication Required"))

          val output = proc
            .call(
              cwd = tmpDir,
              env = Map(
                "COURSIER_CACHE" -> (tmpDir / "cache-1").toString,
                "CS_MAVEN_HOME"  -> okM2Dir.toString
              )
            )
            .out.text()
          val expectedOutput = "foo" + System.lineSeparator()
          assert(output == expectedOutput)
        }
      }
    }

    test("config file authenticated proxy") {
      if (hasDocker) configFileAuthenticatedProxyTest()
      else "Docker test disabled"
    }

    def withAuthProxy[T](f: (String, String, Int) => T): T = {
      val networkName = "cs-test-" + UUID.randomUUID().toString
      var containerId = ""
      try {
        os.proc("docker", "network", "create", networkName)
          .call(stdin = os.Inherit, stdout = os.Inherit)
        val host = {
          val res  = os.proc("docker", "network", "inspect", networkName).call(stdin = os.Inherit)
          val resp = ujson.read(res.out.trim())
          resp.arr(0).apply("IPAM").apply("Config").arr(0).apply("Gateway").str
        }
        val port = {
          val s = new ServerSocket(0)
          try s.getLocalPort
          finally s.close()
        }
        val res = os.proc(
          "docker",
          "run",
          "-d",
          "--rm",
          "-p",
          s"$port:80",
          "--network",
          networkName,
          "bahamat/authenticated-proxy@sha256:568c759ac687f93d606866fbb397f39fe1350187b95e648376b971e9d7596e75"
        )
          .call(stdin = os.Inherit)
        containerId = res.out.trim()
        f(networkName, host, port)
      }
      finally {
        if (containerId.nonEmpty) {
          System.err.println(s"Removing container $containerId")
          os.proc("docker", "rm", "-f", containerId)
            .call(stdin = os.Inherit, stdout = os.Inherit)
        }
        os.proc("docker", "network", "rm", networkName)
          .call(stdin = os.Inherit, stdout = os.Inherit)
      }
    }

    def configFileAuthenticatedProxyTest(): Unit =
      TestUtil.withTempDir { tmpDir0 =>
        val tmpDir = os.Path(tmpDir0, os.pwd)

        os.proc(launcher, "bootstrap", "-o", "cs-echo", "io.get-coursier:echo:1.0.1", extraOptions)
          .call(cwd = tmpDir)

        val output = os.proc("./cs-echo", "foo")
          .call(cwd = tmpDir)
          .out.text()
        val expectedOutput = "foo" + System.lineSeparator()
        assert(output == expectedOutput)

        withAuthProxy { (networkName, proxyHost, proxyPort) =>
          val configContent =
            s"""{
               |  "httpProxy": {
               |    "address": "http://$proxyHost:$proxyPort",
               |    "user": "value:jack",
               |    "password": "value:insecure"
               |  }
               |}
               |""".stripMargin
          val configFile = tmpDir / "config.json"
          os.write(configFile, configContent)

          val header = "*** Running command ***"

          val words = Seq("a", "b", "foo")

          val scriptContent =
            s"""#!/usr/bin/env bash
               |set -e
               |export PATH="$$(pwd)/bin:$$PATH"
               |if ./cs-echo a b foo; then
               |  echo "Expected command to fail at first"
               |  exit 1
               |fi
               |export SCALA_CLI_CONFIG="$$(pwd)/config.json"
               |echo "$header"
               |exec ./cs-echo ${words.map(w => "\"" + w + "\"").mkString(" ")}
               |""".stripMargin
          os.write(tmpDir / "script.sh", scriptContent)

          os.copy(
            os.Path(assembly, os.pwd),
            tmpDir / "bin" / "cs",
            createFolders = true,
            copyAttributes = true
          )

          val res = os.proc(
            "docker",
            "run",
            "--rm",
            "--dns",
            "0.0.0.0",
            "--network",
            networkName,
            "-v",
            s"$tmpDir:/data",
            "-w",
            "/data",
            "eclipse-temurin:17",
            "/bin/bash",
            "./script.sh"
          )
            .call(cwd = tmpDir)
          val output = res.out.text().split(Pattern.quote(header)).last
          assert(output.trim() == words.mkString(" "))
        }
      }

    test("mirror") {
      TestUtil.withTempDir { tmpDir0 =>
        val tmpDir = os.Path(tmpDir0, os.pwd)
        val cache  = tmpDir / "cache"

        val propsLauncher = tmpDir / "props"
        os.proc(
          launcher,
          "bootstrap",
          "io.get-coursier:props:1.0.7",
          "--cache",
          cache,
          "-o",
          propsLauncher
        )
          .call(stdin = os.Inherit, stdout = os.Inherit)

        val urls0 = {
          val zf = new ZipFile(propsLauncher.toIO)
          val e  = zf.getEntry("coursier/bootstrap/launcher/bootstrap-jar-urls")
          val is = zf.getInputStream(e)
          try Source.fromInputStream(is)(Codec.UTF8).mkString.linesIterator.toVector
          finally is.close()
        }

        assert(urls0.nonEmpty)
        assert(urls0.forall(_.startsWith("https://repo1.maven.org/maven2/")))

        val configContent =
          s"""{
             |  "repositories": {
             |    "mirrors": [
             |      "https://maven-central.storage-download.googleapis.com/maven2 = https://repo1.maven.org/maven2"
             |    ]
             |  }
             |}
             |""".stripMargin
        val configFile = tmpDir / "config.json"
        os.write(configFile, configContent)

        val binDir = tmpDir / "bin"
        val ext    = if (Properties.isWin) ".bat" else ""
        os.copy(
          os.Path(assembly, os.pwd),
          binDir / s"cs$ext",
          createFolders = true,
          copyAttributes = true
        )

        val (pathVarName, pathValue) = sys.env
          .find(_._1.toLowerCase(java.util.Locale.ROOT) == "path")
          .getOrElse(("PATH", ""))
        val fullPath =
          Seq(binDir.toString, pathValue).mkString(File.pathSeparator)
        val propsLauncher0 =
          if (Properties.isWin) propsLauncher / os.up / (propsLauncher.last + ".bat")
          else propsLauncher
        val res1 = os.proc(propsLauncher0, "java.class.path")
          .call(
            env = Map(
              "SCALA_CLI_CONFIG" -> configFile.toString,
              "COURSIER_CACHE"   -> cache.toString,
              pathVarName        -> fullPath
            )
          )
        val jars1 = res1.out.trim()
          .split(File.pathSeparator)
          .toVector
          .map(os.Path(_, os.pwd))
          .filter(_ != propsLauncher)
          .map(_.relativeTo(cache))

        val gcsPrefix =
          os.rel / "https" / "maven-central.storage-download.googleapis.com" / "maven2"
        assert(jars1.forall(_.startsWith(gcsPrefix)))
      }
    }

    test("credentials from properties") {
      credentialsTest(useConfig = false)
    }
    test("credentials from config") {
      credentialsTest(useConfig = true)
    }

    def credentialsTest(useConfig: Boolean): Unit = {
      val user     = "alex"
      val password = "secure1234"
      val realm    = "therealm"
      val host     = "127.0.0.1"
      val port = {
        val s = new ServerSocket(0)
        try s.getLocalPort()
        finally s.close()
      }
      TestUtil.withTempDir("credentialstest") { root0 =>
        val root = os.Path(root0)

        val credentialsEnv =
          if (useConfig) {

            val binDir = root / "bin"
            val ext    = if (Properties.isWin) ".bat" else ""
            os.copy(
              os.Path(assembly, os.pwd),
              binDir / s"cs$ext",
              createFolders = true,
              copyAttributes = true
            )

            val (pathKey, currentPath) =
              sys.env.find(_._1.toLowerCase(Locale.ROOT) == "path").getOrElse(("PATH", ""))
            val fullPath =
              Seq(binDir.toString, currentPath).mkString(File.pathSeparator)

            val configContent =
              s"""{
                 |  "repositories": {
                 |    "credentials": [
                 |      {
                 |        "host": "$host",
                 |        "user": "value:$user",
                 |        "password": "value:$password",
                 |        "realm": "$realm",
                 |        "httpsOnly": false,
                 |        "matchHost": true
                 |      }
                 |    ]
                 |  }
                 |}
                 |""".stripMargin
            val configFile = root / "credentials.json"
            os.write(configFile, configContent)
            Map("SCALA_CLI_CONFIG" -> configFile.toString, pathKey -> fullPath)
          }
          else {
            val propertiesContent =
              s"""simple.username=$user
                 |simple.password=$password
                 |simple.host=$host
                 |simple.realm=$realm
                 |simple.https-only=false
                 |simple.auto=true
                 |""".stripMargin
            val propertiesFile = root / "credentials.properties"
            os.write(propertiesFile, propertiesContent)
            Map("COURSIER_CREDENTIALS" -> propertiesFile.toNIO.toUri.toString)
          }

        val propsDep    = "io.get-coursier:props:1.0.7"
        val firstCache  = root / "cache"
        val secondCache = root / "cache-2"
        val thirdCache  = root / "cache-3"
        os.proc(launcher, "fetch", propsDep, "--cache", firstCache)
          .call(cwd = root, stdin = os.Inherit, stdout = os.Inherit)
        val proc = os.proc(
          launcher,
          "launch",
          "io.get-coursier:http-server_2.12:1.0.1",
          "--",
          "--user",
          user,
          "--password",
          password,
          "--realm",
          realm,
          "--directory",
          firstCache / "https" / "repo1.maven.org" / "maven2",
          "--host",
          host,
          "--port",
          port,
          "-v"
        )
          .spawn(cwd = root, mergeErrIntoOut = true)
        try {

          // a timeout around this would be great…
          System.err.println(s"Waiting for local HTTP server to get started on $host:$port…")
          var lineOpt = Option.empty[String]
          while (
            proc.isAlive() && {
              lineOpt = Some(proc.stdout.readLine())
              !lineOpt.exists(_.startsWith("Listening on "))
            }
          )
            for (l <- lineOpt)
              System.err.println(l)

          // Seems required, especially when using native launchers
          val waitFor = Duration(2L, "s")
          System.err.println(s"Waiting $waitFor")
          Thread.sleep(waitFor.toMillis)

          val t = new Thread("test-http-server-output") {
            setDaemon(true)
            override def run(): Unit = {
              var line = ""
              while (
                proc.isAlive() && {
                  line = proc.stdout.readLine()
                  line != null
                }
              )
                System.err.println(line)
            }
          }
          t.start()

          val propsLauncher = root / "props"
          os.proc(
            launcher,
            "bootstrap",
            "--no-default",
            "-r",
            s"http://$host:$port",
            propsDep,
            "-o",
            propsLauncher,
            "--cache",
            secondCache
          )
            .call(cwd = root, env = credentialsEnv)

          val propsLauncher0 =
            if (Properties.isWin) propsLauncher / os.up / s"${propsLauncher.last}.bat"
            else propsLauncher
          val res = os.proc(propsLauncher0, "java.class.path")
            .call(
              cwd = root,
              env = Map("COURSIER_CACHE" -> thirdCache.toString) ++ credentialsEnv
            )
          val propsBootstrapCp =
            res.out.trim().split(File.pathSeparator).toVector.map(os.Path(_, root))
          val actualCp = propsBootstrapCp.filter(_ != propsLauncher)
          assert(actualCp.nonEmpty)
          assert(actualCp.forall(_.startsWith(thirdCache)))
        }
        finally {
          proc.destroy()
          Thread.sleep(100L)
          if (proc.isAlive()) {
            Thread.sleep(1000L)
            proc.destroyForcibly()
          }
        }
      }
    }

    test("hybrid with coursier dependency") {
      TestUtil.withTempDir("hybrid-cs") { tmpDir =>
        os.proc(
          launcher,
          "bootstrap",
          "--hybrid",
          "sh.almond:::scala-kernel:0.13.6",
          "--shared",
          "sh.almond:::scala-kernel-api",
          "-r",
          "jitpack",
          "--scala",
          "2.12.17",
          "-o",
          "almond212"
        ).call(cwd = os.Path(tmpDir, os.pwd))
      }
    }

    test("jni-utils from bootstrap") {
      if (Properties.isWin)
        jniUtilFromBootstrapTest()
      else
        "disabled"
    }

    test("jni-utils from hybrid bootstrap") {
      if (Properties.isWin)
        jniUtilFromBootstrapTest("--hybrid")
      else
        "disabled"
    }

    test("jni-utils from standalone bootstrap") {
      if (Properties.isWin)
        jniUtilFromBootstrapTest("--standalone")
      else
        "disabled"
    }

    def jniUtilFromBootstrapTest(extraOpts: String*): Unit = {
      TestUtil.withTempDir("jni-cs") { tmpDir0 =>
        val tmpDir = os.Path(tmpDir0, os.pwd)

        val repo        = tmpDir / "repo"
        val appLauncher = tmpDir / "app"
        val actualAppLauncher =
          if (Properties.isWin) tmpDir / "app.bat"
          else appLauncher

        val appSource = tmpDir / "TestApp.scala"
        os.write(
          appSource,
          """//> using scala "2.13.10"
            |//> using lib "io.get-coursier.jniutils:windows-jni-utils:0.3.3"
            |//> using publish.organization "io.get-coursier.tests"
            |//> using publish.name "test-app"
            |//> using publish.version "0.1.0"
            |
            |package testapp
            |
            |import coursier.jniutils.WindowsEnvironmentVariables
            |
            |object TestApp {
            |  def main(args: Array[String]): Unit = {
            |    val path = WindowsEnvironmentVariables.get("PATH")
            |    System.err.println(s"PATH=$path")
            |  }
            |}
            |""".stripMargin
        )

        os.proc(
          TestUtil.scalaCli,
          "--power",
          "publish",
          "--server=false",
          "--publish-repo",
          repo.toNIO.toUri.toASCIIString,
          appSource
        )
          .call(cwd = tmpDir, stdin = os.Inherit, stdout = os.Inherit)

        os.proc(
          launcher,
          "bootstrap",
          "-r",
          repo.toNIO.toUri.toASCIIString,
          "io.get-coursier.tests::test-app:0.1.0",
          "--scala",
          "2.13.10",
          "-o",
          appLauncher,
          extraOpts
        ).call(cwd = tmpDir, stdin = os.Inherit, stdout = os.Inherit)

        os.proc(actualAppLauncher)
          .call(cwd = tmpDir, stdin = os.Inherit, stdout = os.Inherit)

        os.proc("java", "-Dcoursier.jni.check.throw=true", "-jar", appLauncher)
          .call(cwd = tmpDir, stdin = os.Inherit, stdout = os.Inherit)
      }
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy