summaryrefslogblamecommitdiff
path: root/main/test/src/mill/eval/EvaluationTests.scala
blob: 9c215086754957b457c8fa5b8596c183eef96f5e (plain) (tree)
1
2
3
4
5
6
7
8
9
                 
 
 
                                      
                                                  
                       
                                                                   
                           
              
                               
 
                     
 
                                         
                                                                             
                                                                               
 
                                                       

                                             
                                      

                                                                                     
                                   



                                                                                    
 
                                                  
 

                                                              

             
                                       
                                                        
                                                        


                                                                         
                         

                                                     
               

                                                        



         
 

                    
                                      
                   
                       
                       
 

                          
                                          
                                             
                                                            


                                                                  
                                                            


                     
                                     
                                                            

                         
                                                        

                       
                                                            


                           
                                           
                                   
                                                                    

                         
                                                                

                       
                                                                    
 
                                                       
 
                                                                


                        
                                        
                                                                         

                         
                                                        


                                                                                    
                                                                         

                         
                                                              

                          
                                                               


                            
                                            

                                                              
                                                                         

                         
                                                                     


                                                                                    
                                                                         

                         
                                                                     

                          
                                                                     
       
 

                                  
                                                  
 
                                                                                   
 
                      
                                                                   
 

                                                                           
                                                                      


                                                                                
                                                                                
       
     
 
                      


                                                                              
 
                               
                                                 
                                                                  
                                                                           

                                                                  
                                                                           
                                  
                           
                                                                  
                                                                           
                                               


       
                       

                             
                                               

                                                             
 

                             
                                   
 
                                                     

                                                       


                                
 
                                      
 
                                                        

                                                                                    
       
 
                            


                                                                               

                                 
                                                   
                                                                       

 
                                                                                      
                                          
                                            
                                                                                             













                                                                                
                                                   








                                  
                                                                                      
                                          
                                            
                                                                                          






                                        
       

























                                                                             
 
                           


                                                                               




                               
                                                 


                             
                                   





                                                       
                      











                                                                                  
                                      
                                               
                                                                                        



                                                                         
                                                        






                                                                               
                                                                                 



                                                                              
                                                                                            
                                                                 
                                                                                            

                                                                 
                                                                                                
                                                                 
                                                                                                

                                                                 
     

   
package mill.eval


import mill.util.TestUtil.{Test, test}
import mill.define.{Discover, Graph, Target, Task}
import mill.{Module, T}
import mill.util.{DummyLogger, TestEvaluator, TestGraphs, TestUtil}
import mill.util.Strict.Agg
import utest._
import utest.framework.TestPath

import ammonite.ops._

object EvaluationTests extends TestSuite{
  class Checker[T <: TestUtil.BaseModule](module: T)(implicit tp: TestPath) {
    // Make sure data is persisted even if we re-create the evaluator each time

    def evaluator = new TestEvaluator(module).evaluator

    def apply(target: Task[_], expValue: Any,
              expEvaled: Agg[Task[_]],
              // How many "other" tasks were evaluated other than those listed above.
              // Pass in -1 to skip the check entirely
              extraEvaled: Int = 0,
              // Perform a second evaluation of the same tasks, and make sure the
              // outputs are the same but nothing was evaluated. Disable this if you
              // are directly evaluating tasks which need to re-evaluate every time
              secondRunNoOp: Boolean = true) = {

      val evaled = evaluator.evaluate(Agg(target))

      val (matchingReturnedEvaled, extra) =
        evaled.evaluated.indexed.partition(expEvaled.contains)

      assert(
        evaled.values == Seq(expValue),
        matchingReturnedEvaled.toSet == expEvaled.toSet,
        extraEvaled == -1 || extra.length == extraEvaled
      )

      // Second time the value is already cached, so no evaluation needed
      if (secondRunNoOp){
        val evaled2 = evaluator.evaluate(Agg(target))
        val expecteSecondRunEvaluated = Agg()
        assert(
          evaled2.values == evaled.values,
          evaled2.evaluated == expecteSecondRunEvaluated
        )
      }
    }
  }


  val tests = Tests{
    object graphs extends TestGraphs()
    import graphs._
    import TestGraphs._
    'evaluateSingle - {

      'singleton - {
        import singleton._
        val check = new Checker(singleton)
        // First time the target is evaluated
        check(single, expValue = 0, expEvaled = Agg(single))

        single.counter += 1
        // After incrementing the counter, it forces re-evaluation
        check(single, expValue = 1, expEvaled = Agg(single))
      }
      'pair - {
        import pair._
        val check = new Checker(pair)
        check(down, expValue = 0, expEvaled = Agg(up, down))

        down.counter += 1
        check(down, expValue = 1, expEvaled = Agg(down))

        up.counter += 1
        check(down, expValue = 2, expEvaled = Agg(up, down))
      }
      'anonTriple - {
        import anonTriple._
        val check = new Checker(anonTriple)
        val middle = down.inputs(0)
        check(down, expValue = 0, expEvaled = Agg(up, middle, down))

        down.counter += 1
        check(down, expValue = 1, expEvaled = Agg(middle, down))

        up.counter += 1
        check(down, expValue = 2, expEvaled = Agg(up, middle, down))

        middle.asInstanceOf[TestUtil.Test].counter += 1

        check(down, expValue = 3, expEvaled = Agg(middle, down))
      }
      'diamond - {
        import diamond._
        val check = new Checker(diamond)
        check(down, expValue = 0, expEvaled = Agg(up, left, right, down))

        down.counter += 1
        check(down, expValue = 1, expEvaled = Agg(down))

        up.counter += 1
        // Increment by 2 because up is referenced twice: once by left once by right
        check(down, expValue = 3, expEvaled = Agg(up, left, right, down))

        left.counter += 1
        check(down, expValue = 4, expEvaled = Agg(left, down))

        right.counter += 1
        check(down, expValue = 5, expEvaled = Agg(right, down))
      }
      'anonDiamond - {
        import anonDiamond._
        val check = new Checker(anonDiamond)
        val left = down.inputs(0).asInstanceOf[TestUtil.Test]
        val right = down.inputs(1).asInstanceOf[TestUtil.Test]
        check(down, expValue = 0, expEvaled = Agg(up, left, right, down))

        down.counter += 1
        check(down, expValue = 1, expEvaled = Agg(left, right, down))

        up.counter += 1
        // Increment by 2 because up is referenced twice: once by left once by right
        check(down, expValue = 3, expEvaled = Agg(up, left, right, down))

        left.counter += 1
        check(down, expValue = 4, expEvaled = Agg(left, right, down))

        right.counter += 1
        check(down, expValue = 5, expEvaled = Agg(left, right, down))
      }

      'bigSingleTerminal - {
        import bigSingleTerminal._
        val check = new Checker(bigSingleTerminal)

        check(j, expValue = 0, expEvaled = Agg(a, b, e, f, i, j), extraEvaled = 22)

        j.counter += 1
        check(j, expValue = 1, expEvaled = Agg(j), extraEvaled = 3)

        i.counter += 1
        // increment value by 2 because `i` is used twice on the way to `j`
        check(j, expValue = 3, expEvaled = Agg(j, i), extraEvaled = 8)

        b.counter += 1
        // increment value by 4 because `b` is used four times on the way to `j`
        check(j, expValue = 7, expEvaled = Agg(b, e, f, i, j), extraEvaled = 20)
      }
    }

    'evaluateMixed - {
      'separateGroups - {
        // Make sure that `left` and `right` are able to recompute separately,
        // even though one depends on the other

        import separateGroups._
        val checker = new Checker(separateGroups)
        val evaled1 = checker.evaluator.evaluate(Agg(right, left))
        val filtered1 = evaled1.evaluated.filter(_.isInstanceOf[Target[_]])
        assert(filtered1 == Agg(change, left, right))
        val evaled2 = checker.evaluator.evaluate(Agg(right, left))
        val filtered2 = evaled2.evaluated.filter(_.isInstanceOf[Target[_]])
        assert(filtered2 == Agg())
        change.counter += 1
        val evaled3 = checker.evaluator.evaluate(Agg(right, left))
        val filtered3 = evaled3.evaluated.filter(_.isInstanceOf[Target[_]])
        assert(filtered3 == Agg(change, right))


      }
      'triangleTask - {

        import triangleTask._
        val checker = new Checker(triangleTask)
        checker(right, 3, Agg(left, right), extraEvaled = -1)
        checker(left, 1, Agg(), extraEvaled = -1)

      }
      'multiTerminalGroup - {
        import multiTerminalGroup._

        val checker = new Checker(multiTerminalGroup)
        checker(right, 1, Agg(right), extraEvaled = -1)
        checker(left, 1, Agg(left), extraEvaled = -1)
      }

      'multiTerminalBoundary - {

        import multiTerminalBoundary._

        val checker = new Checker(multiTerminalBoundary)
        checker(task2, 4, Agg(right, left), extraEvaled = -1, secondRunNoOp = false)
        checker(task2, 4, Agg(), extraEvaled = -1, secondRunNoOp = false)
      }

      'overrideSuperTask - {
        // Make sure you can override targets, call their supers, and have the
        // overriden target be allocated a spot within the overriden/ folder of
        // the main publically-available target
        import canOverrideSuper._

        val checker = new Checker(canOverrideSuper)
        checker(foo, Seq("base", "object"), Agg(foo), extraEvaled = -1)


        val public = ammonite.ops.read(checker.evaluator.outPath / 'foo / "meta.json")
        val overriden = ammonite.ops.read(
          checker.evaluator.outPath / 'foo /
            'overriden / "mill" / "util" / "TestGraphs" / "BaseModule" / "foo"  / "meta.json"
        )
        assert(
          public.contains("base"),
          public.contains("object"),
          overriden.contains("base"),
          !overriden.contains("object")
        )
      }
      'overrideSuperCommand - {
        // Make sure you can override commands, call their supers, and have the
        // overriden command be allocated a spot within the overriden/ folder of
        // the main publically-available command
        import canOverrideSuper._

        val checker = new Checker(canOverrideSuper)
        val runCmd = cmd(1)
        checker(
          runCmd,
          Seq("base1", "object1"),
          Agg(runCmd),
          extraEvaled = -1,
          secondRunNoOp = false
        )

        val public = ammonite.ops.read(checker.evaluator.outPath / 'cmd / "meta.json")
        val overriden = ammonite.ops.read(
          checker.evaluator.outPath / 'cmd /
          'overriden / "mill" / "util" / "TestGraphs" / "BaseModule"/ "cmd"  / "meta.json"
        )
        assert(
          public.contains("base1"),
          public.contains("object1"),
          overriden.contains("base1"),
          !overriden.contains("object1")
        )
      }
      'nullTasks - {
        import nullTasks._
        val checker = new Checker(nullTasks)
        checker(nullTarget1, null, Agg(nullTarget1), extraEvaled = -1)
        checker(nullTarget1, null, Agg(), extraEvaled = -1)
        checker(nullTarget2, null, Agg(nullTarget2), extraEvaled = -1)
        checker(nullTarget2, null, Agg(), extraEvaled = -1)
        checker(nullTarget3, null, Agg(nullTarget3), extraEvaled = -1)
        checker(nullTarget3, null, Agg(), extraEvaled = -1)
        checker(nullTarget4, null, Agg(nullTarget4), extraEvaled = -1)
        checker(nullTarget4, null, Agg(), extraEvaled = -1)

        val nc1 = nullCommand1()
        val nc2 = nullCommand2()
        val nc3 = nullCommand3()
        val nc4 = nullCommand4()

        checker(nc1, null, Agg(nc1), extraEvaled = -1, secondRunNoOp = false)
        checker(nc1, null, Agg(nc1), extraEvaled = -1, secondRunNoOp = false)
        checker(nc2, null, Agg(nc2), extraEvaled = -1, secondRunNoOp = false)
        checker(nc2, null, Agg(nc2), extraEvaled = -1, secondRunNoOp = false)
        checker(nc3, null, Agg(nc3), extraEvaled = -1, secondRunNoOp = false)
        checker(nc3, null, Agg(nc3), extraEvaled = -1, secondRunNoOp = false)
        checker(nc4, null, Agg(nc4), extraEvaled = -1, secondRunNoOp = false)
        checker(nc4, null, Agg(nc4), extraEvaled = -1, secondRunNoOp = false)
      }

      'tasksAreUncached - {
        // Make sure the tasks `left` and `middle` re-compute every time, while
        // the target `right` does not
        //
        //    ___ left ___
        //   /            \
        // up    middle -- down
        //                /
        //           right
        object build extends TestUtil.BaseModule{
          var leftCount = 0
          var rightCount = 0
          var middleCount = 0
          def up = T{ test.anon() }
          def left = T.task{ leftCount += 1; up() + 1 }
          def middle = T.task{ middleCount += 1; 100 }
          def right = T{ rightCount += 1; 10000 }
          def down = T{ left() + middle() + right() }
        }

        import build._

        // Ensure task objects themselves are not cached, and recomputed each time
        assert(
          up eq up,
          left ne left,
          middle ne middle,
          right eq right,
          down eq down
        )

        // During the first evaluation, they get computed normally like any
        // cached target
        val check = new Checker(build)
        assert(leftCount == 0, rightCount == 0)
        check(down, expValue = 10101, expEvaled = Agg(up, right, down), extraEvaled = 8)
        assert(leftCount == 1, middleCount == 1, rightCount == 1)

        // If the upstream `up` doesn't change, the entire block of tasks
        // doesn't need to recompute
        check(down, expValue = 10101, expEvaled = Agg())
        assert(leftCount == 1, middleCount == 1, rightCount == 1)

        // But if `up` changes, the entire block of downstream tasks needs to
        // recompute together, including `middle` which doesn't depend on `up`,
        // because tasks have no cached value that can be used. `right`, which
        // is a cached Target, does not recompute
        up.inputs(0).asInstanceOf[Test].counter += 1
        check(down, expValue = 10102, expEvaled = Agg(up, down), extraEvaled = 6)
        assert(leftCount == 2, middleCount == 2, rightCount == 1)

        // Running the tasks themselves results in them being recomputed every
        // single time, even if nothing changes
        check(left, expValue = 2, expEvaled = Agg(), extraEvaled = 1, secondRunNoOp = false)
        assert(leftCount == 3, middleCount == 2, rightCount == 1)
        check(left, expValue = 2, expEvaled = Agg(), extraEvaled = 1, secondRunNoOp = false)
        assert(leftCount == 4, middleCount == 2, rightCount == 1)

        check(middle, expValue = 100, expEvaled = Agg(), extraEvaled = 2, secondRunNoOp = false)
        assert(leftCount == 4, middleCount == 3, rightCount == 1)
        check(middle, expValue = 100, expEvaled = Agg(), extraEvaled = 2, secondRunNoOp = false)
        assert(leftCount == 4, middleCount == 4, rightCount == 1)
      }
    }
  }
}