summaryrefslogtreecommitdiff
path: root/main/test/src/eval/JavaCompileJarTests.scala
blob: e243b915a9f41080977c8126a99671ccd4aa95b6 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
package mill.eval

import mill.define.{Discover, Input, Target, Task}
import mill.modules.Jvm
import mill.modules.Jvm.JarManifest
import mill.api.Ctx.Dest
import mill.{Module, T}
import mill.util.{DummyLogger, TestEvaluator, TestUtil}
import mill.api.Strict.Agg
import mill.api.Loose
import utest._
import mill._

object JavaCompileJarTests extends TestSuite{
  def compileAll(sources: mill.api.Loose.Agg[PathRef])(implicit ctx: Dest) = {
    os.makeDir.all(ctx.dest)

    os.proc("javac", sources.map(_.path.toString()).toSeq, "-d", ctx.dest).call(ctx.dest)
    PathRef(ctx.dest)
  }

  val tests = Tests{
    'javac {
      val javacSrcPath = os.pwd / 'main / 'test / 'resources / 'examples / 'javac
      val javacDestPath =  TestUtil.getOutPath() / 'src

      os.makeDir.all(javacDestPath / os.up)
      os.copy(javacSrcPath, javacDestPath)

      object Build extends TestUtil.BaseModule{
        def sourceRootPath = javacDestPath / 'src
        def resourceRootPath = javacDestPath / 'resources

        // sourceRoot -> allSources -> classFiles
        //                                |
        //                                v
        //           resourceRoot ---->  jar
        def sourceRoot = T.sources{ sourceRootPath }
        def resourceRoot = T.sources{ resourceRootPath }
        def allSources = T{ sourceRoot().flatMap(p => os.walk(p.path)).map(PathRef(_)) }
        def classFiles = T{ compileAll(allSources()) }
        def jar = T{ Jvm.createJar(Loose.Agg(classFiles().path) ++ resourceRoot().map(_.path)) }
        // Test createJar() with optional file filter.
        def filterJar(fileFilter: (os.Path, os.RelPath) => Boolean) = T{ Jvm.createJar(Loose.Agg(classFiles().path) ++ resourceRoot().map(_.path), JarManifest.Default, fileFilter) }

        def run(mainClsName: String) = T.command{
          os.proc('java, "-Duser.language=en", "-cp", classFiles().path, mainClsName).call()
        }
      }

      import Build._

      var evaluator = new TestEvaluator(Build)
      def eval[T](t: Task[T]) = {
        evaluator.apply(t)
      }
      def check(targets: Agg[Task[_]], expected: Agg[Task[_]]) = {
        evaluator.check(targets, expected)
      }

      def append(path: os.Path, txt: String) = ammonite.ops.write.append(path, txt)


      check(
        targets = Agg(jar),
        expected = Agg(allSources, classFiles, jar)
      )

      // Re-running with no changes results in nothing being evaluated
      check(targets = Agg(jar), expected = Agg())
      // Appending an empty string gets ignored due to file-content hashing
      append(sourceRootPath / "Foo.java", "")
      check(targets = Agg(jar), expected = Agg())

      // Appending whitespace forces a recompile, but the classfilesend up
      // exactly the same so no re-jarring.
      append(sourceRootPath / "Foo.java", " ")
      // Note that `sourceRoot` and `resourceRoot` never turn up in the `expected`
      // list, because they are `Source`s not `Target`s
      check(targets = Agg(jar), expected = Agg(/*sourceRoot, */allSources, classFiles))

      // Appending a new class changes the classfiles, which forces us to
      // re-create the final jar
      append(sourceRootPath / "Foo.java", "\nclass FooTwo{}")
      check(targets = Agg(jar), expected = Agg(allSources, classFiles, jar))

      // Tweaking the resources forces rebuild of the final jar, without
      // recompiling classfiles
      append(resourceRootPath / "hello.txt", " ")
      check(targets = Agg(jar), expected = Agg(jar))

      // You can swap evaluators halfway without any ill effects
      evaluator = new TestEvaluator(Build)

      // Asking for an intermediate target forces things to be build up to that
      // target only; these are re-used for any downstream targets requested
      append(sourceRootPath / "Bar.java", "\nclass BarTwo{}")
      append(resourceRootPath / "hello.txt", " ")
      check(targets = Agg(classFiles), expected = Agg(allSources, classFiles))
      check(targets = Agg(jar), expected = Agg(jar))
      check(targets = Agg(allSources), expected = Agg())

      append(sourceRootPath / "Bar.java", "\nclass BarThree{}")
      append(resourceRootPath / "hello.txt", " ")
      check(targets = Agg(resourceRoot), expected = Agg())
      check(targets = Agg(allSources), expected = Agg(allSources))
      check(targets = Agg(jar), expected = Agg(classFiles, jar))

      val jarContents = os.proc('jar, "-tf", evaluator.outPath/'jar/'dest/"out.jar").call(evaluator.outPath).out.string
      val expectedJarContents =
        """META-INF/MANIFEST.MF
          |test/Bar.class
          |test/BarThree.class
          |test/BarTwo.class
          |test/Foo.class
          |test/FooTwo.class
          |hello.txt
          |""".stripMargin
      assert(jarContents.linesIterator.toSeq == expectedJarContents.linesIterator.toSeq)

      // Create the Jar again, but this time, filter out the Foo files.
      def noFoos(s: String) = !s.contains("Foo")
      val filterFunc = (p: os.Path, r: os.RelPath) => noFoos(r.last)
      eval(filterJar(filterFunc))
      val filteredJarContents = os.proc('jar, "-tf", evaluator.outPath/'filterJar/'dest/"out.jar").call(evaluator.outPath).out.string
      assert(filteredJarContents.linesIterator.toSeq == expectedJarContents.linesIterator.filter(noFoos(_)).toSeq)

      val executed = os.proc('java, "-cp", evaluator.outPath/'jar/'dest/"out.jar", "test.Foo").call(evaluator.outPath).out.string
      assert(executed == (31337 + 271828) + System.lineSeparator)

      for(i <- 0 until 3){
        // Build.run is not cached, so every time we eval it it has to
        // re-evaluate
        val Right((runOutput, evalCount)) = eval(Build.run("test.Foo"))
        assert(
          runOutput.out.string == (31337 + 271828) + System.lineSeparator,
          evalCount == 1
        )
      }

      val Left(Result.Exception(ex, _)) = eval(Build.run("test.BarFour"))

      assert(ex.getMessage.contains("Could not find or load main class"))

      append(
        sourceRootPath / "Bar.java",
        """
        class BarFour{
          public static void main(String[] args){
            System.out.println("New Cls!");
          }
        }
        """
      )
      val Right((runOutput2, evalCount2)) = eval(Build.run("test.BarFour"))
      assert(
        runOutput2.out.string == "New Cls!" + System.lineSeparator,
        evalCount2 == 3
      )
      val Right((runOutput3, evalCount3)) = eval(Build.run("test.BarFour"))
      assert(
        runOutput3.out.string == "New Cls!" + System.lineSeparator,
        evalCount3 == 1
      )
    }
  }
}