summaryrefslogtreecommitdiff
path: root/plugins/groovy/test/org/jetbrains/plugins/groovy/compiler/GroovyCompilerTest.groovy
blob: d6363fb560db86c77c250b4b291b1b318edd55ee (plain)
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
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
/*
 * Copyright 2000-2009 JetBrains s.r.o.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package org.jetbrains.plugins.groovy.compiler

import com.intellij.compiler.CompilerConfiguration
import com.intellij.compiler.CompilerConfigurationImpl
import com.intellij.execution.executors.DefaultRunExecutor
import com.intellij.execution.impl.DefaultJavaProgramRunner
import com.intellij.execution.process.ProcessAdapter
import com.intellij.execution.process.ProcessEvent
import com.intellij.execution.process.ProcessHandler
import com.intellij.execution.process.ProcessOutputTypes
import com.intellij.execution.runners.ProgramRunner
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.PathManager
import com.intellij.openapi.compiler.CompilerMessage
import com.intellij.openapi.compiler.CompilerMessageCategory
import com.intellij.openapi.compiler.options.ExcludeEntryDescription
import com.intellij.openapi.compiler.options.ExcludedEntriesConfiguration
import com.intellij.openapi.module.Module
import com.intellij.openapi.roots.ModuleRootModificationUtil
import com.intellij.openapi.util.Key
import com.intellij.openapi.util.Ref
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiFile
import com.intellij.testFramework.PsiTestUtil
import com.intellij.testFramework.TestLoggerFactory
import org.jetbrains.annotations.NotNull
import org.jetbrains.plugins.groovy.lang.psi.GroovyFile

/**
 * @author peter
 */
public class GroovyCompilerTest extends GroovyCompilerTestCase {
  @Override protected void setUp() {
    super.setUp();
    addGroovyLibrary(myModule);
  }

  public void testPlainGroovy() throws Throwable {
    myFixture.addFileToProject("A.groovy", "println '239'");
    assertEmpty(make());
    assertOutput("A", "239");
  }

  public void testJavaDependsOnGroovy() throws Throwable {
    myFixture.addClass("public class Foo {" +
                       "public static void main(String[] args) { " +
                       "  System.out.println(new Bar().foo());" +
                       "}" +
                       "}");
    myFixture.addFileToProject("Bar.groovy", "class Bar {" +
                                             "  def foo() {" +
                                             "    239" +
                                             "  }" +
                                             "}");
    make();
    assertOutput("Foo", "239");
  }

  public void testCorrectFailAndCorrect() throws Exception {
    myFixture.addClass("public class Foo {" +
                       "public static void main(String[] args) { " +
                       "  System.out.println(new Bar().foo());" +
                       "}" +
                       "}");
    final String barText = "class Bar {" + "  def foo() { 239  }" + "}";
    final PsiFile file = myFixture.addFileToProject("Bar.groovy", barText);
    make()
    assertOutput("Foo", "239");

    setFileText(file, "class Bar {}");
    shouldFail { make() }

    setFileText(file, barText);
    make();
    assertOutput("Foo", "239");
  }

  private void shouldFail(Closure action) {
    List<CompilerMessage> messages = action()
    assert messages.find { it.category == CompilerMessageCategory.ERROR }
  }

  public void testRenameToJava() throws Throwable {
    myFixture.addClass("public class Foo {" +
                       "public static void main(String[] args) { " +
                       "  System.out.println(new Bar().foo());" +
                       "}" +
                       "}");

    final PsiFile bar =
      myFixture.addFileToProject("Bar.groovy", "public class Bar {" + "public int foo() { " + "  return 239;" + "}" + "}");

    make();
    assertOutput("Foo", "239");

    setFileName bar, "Bar.java"

    make();
    assertOutput("Foo", "239");
  }

  public void testTransitiveJavaDependency() throws Throwable {
    final VirtualFile ifoo = myFixture.addClass("public interface IFoo { int foo(); }").getContainingFile().getVirtualFile();
    myFixture.addClass("public class Foo implements IFoo {" +
                       "  public int foo() { return 239; }" +
                       "}");
    final PsiFile bar = myFixture.addFileToProject("Bar.groovy", "class Bar {" +
                                                                 "Foo foo\n" +
                                                                 "public static void main(String[] args) { " +
                                                                 "  System.out.println(new Foo().foo());" +
                                                                 "}" +
                                                                 "}");
    assertEmpty(make());
    assertOutput("Bar", "239");

    touch(ifoo);
    touch(bar.getVirtualFile());

    //assertTrue(assertOneElement(make()).contains("WARNING: Groovyc stub generation failed"));
    assertEmpty make()
    assertOutput("Bar", "239");
  }

  public void testTransitiveJavaDependencyThroughGroovy() throws Throwable {
    myFixture.addClass("public class IFoo { void foo() {} }").getContainingFile().getVirtualFile();
    myFixture.addFileToProject("Foo.groovy", "class Foo {\n" +
                                             "  static IFoo f\n" +
                                             "  public int foo() { return 239; }\n" +
                                             "}");
    final PsiFile bar = myFixture.addFileToProject("Bar.groovy", "class Bar extends Foo {" +
                                                                 "public static void main(String[] args) { " +
                                                                 "  System.out.println(new Foo().foo());" +
                                                                 "}" +
                                                                 "}");
    assertEmpty(make());
    assertOutput("Bar", "239");

    deleteClassFile("IFoo");
    touch(bar.getVirtualFile());

    //assertTrue(assertOneElement(make()).contains("WARNING: Groovyc error"));
    assertEmpty make()
    assertOutput("Bar", "239");
  }

  public void testTransitiveGroovyDependency() throws Throwable {
    def foo = myFixture.addFileToProject('Foo.groovy', 'class Foo {} ')
    def bar = myFixture.addFileToProject('Bar.groovy', 'class Bar extends Foo {}')
    def goo = myFixture.addFileToProject('Goo.groovy', 'class Goo extends Bar {}')
    assertEmpty(make());

    touch(foo.virtualFile)
    touch(goo.virtualFile)
    assertEmpty(make());
  }

  public void testJavaDependsOnGroovyEnum() throws Throwable {
    myFixture.addFileToProject("Foo.groovy", "enum Foo { FOO }")
    myFixture.addClass("class Bar { Foo f; }")
    assertEmpty(make())
  }

  public void testDeleteTransitiveJavaClass() throws Throwable {
    myFixture.addClass("public interface IFoo { int foo(); }");
    myFixture.addClass("public class Foo implements IFoo {" +
                       "  public int foo() { return 239; }" +
                       "}");
    final PsiFile bar = myFixture.addFileToProject("Bar.groovy", "class Bar {" +
                                                                 "Foo foo\n" +
                                                                 "public static void main(String[] args) { " +
                                                                 "  System.out.println(new Foo().foo());" +
                                                                 "}" +
                                                                 "}");
    assertEmpty(make());
    assertOutput("Bar", "239");

    deleteClassFile("IFoo");
    touch(bar.getVirtualFile());

    //assertTrue(assertOneElement(make()).contains("WARNING: Groovyc stub generation failed"));
    assertEmpty make()
    assertOutput("Bar", "239");
  }

  public void testGroovyDependsOnGroovy() throws Throwable {
    myFixture.addClass("public class JustToMakeGroovyGenerateStubs {}");
    myFixture.addFileToProject("Foo.groovy", "class Foo { }");
    final PsiFile bar = myFixture.addFileToProject("Bar.groovy", "class Bar {" +
                                                                 "def foo(Foo f) {}\n" +
                                                                 "public static void main(String[] args) { " +
                                                                 "  System.out.println(239);" +
                                                                 "}" +
                                                                 "}");
    assertEmpty(make());
    assertOutput("Bar", "239");

    touch(bar.getVirtualFile());

    assertEmpty(make());
    assertOutput("Bar", "239");
  }

  @Override
  void runBare() {
    new File(TestLoggerFactory.testLogDir, "../log/build-log/build.log").delete()
    super.runBare()
  }

  @Override
  void runTest() {
    try {
      super.runTest()
    }
    catch (Throwable e) {
      printLogs()
      throw e
    }
  }

  private static void printLogs() {
    def ideaLog = new File(TestLoggerFactory.testLogDir, "idea.log")
    if (ideaLog.exists()) {
      println "\n\nIdea Log:"
      def limit = 20000
      def logText = ideaLog.text
      println(logText.size() < limit ? logText : logText.substring(logText.size() - limit))
    }
    def makeLog = new File(TestLoggerFactory.testLogDir, "../log/build-log/build.log")
    if (makeLog.exists()) {
      println "\n\nServer Log:"
      println makeLog.text
    }
    System.out.flush()
  }

  public void testMakeInTests() throws Throwable {
    setupTestSources();
    myFixture.addFileToProject("tests/Super.groovy", "class Super {}");
    assertEmpty(make());

    def sub = myFixture.addFileToProject("tests/Sub.groovy", "class Sub {\n" +
      "  Super xxx() {}\n" +
      "  static void main(String[] args) {" +
      "    println 'hello'" +
      "  }" +
      "}");

    def javaFile = myFixture.addFileToProject("tests/Java.java", "public class Java {}");

    assertEmpty(make());
    assertOutput("Sub", "hello");
  }

  public void testTestsDependOnProduction() throws Throwable {
    setupTestSources();
    myFixture.addFileToProject("src/com/Bar.groovy", "package com\n" +
                                                     "class Bar {}");
    myFixture.addFileToProject("src/com/ToGenerateStubs.java", "package com;\n" +
                                                               "public class ToGenerateStubs {}");
    myFixture.addFileToProject("tests/com/BarTest.groovy", "package com\n" +
                                                           "class BarTest extends Bar {}");
    assertEmpty(make());
  }

  public void testStubForGroovyExtendingJava() throws Exception {
    def foo = myFixture.addFileToProject("Foo.groovy", "class Foo extends Goo { }");
    myFixture.addFileToProject("Goo.groovy", "class Goo extends Main { void bar() { println 'hello' } }");
    def main = myFixture.addClass("public class Main { public static void main(String[] args) { new Goo().bar(); } }");

    assertEmpty(make());
    assertOutput 'Main', 'hello'

    touch(foo.virtualFile)
    touch(main.containingFile.virtualFile)
    assertEmpty(make());

    assertOutput 'Main', 'hello'
  }

  public void testDontApplyTransformsFromSameModule() throws Exception {
    addTransform();

    myFixture.addClass("public class JavaClassToGenerateStubs {}");

    assertEmpty(make());

  }

  private void addTransform() throws IOException {
    myFixture.addFileToProject("Transf.groovy", """
import org.codehaus.groovy.ast.*
import org.codehaus.groovy.control.*
import org.codehaus.groovy.transform.*
@GroovyASTTransformation(phase = CompilePhase.CONVERSION)
public class Transf implements ASTTransformation {
  void visit(ASTNode[] nodes, SourceUnit sourceUnit) {
    ModuleNode module = nodes[0]
    for (clazz in module.classes) {
      if (clazz.name.contains('Bar')) {
        module.addStaticStarImport('Foo', ClassHelper.makeWithoutCaching(Foo.class))
      }
    }
  }
}""");

    myFixture.addFileToProject("Foo.groovy", "class Foo {\n" +
                                             "static def autoImported() { 239 }\n" +
                                             "}");

    CompilerConfiguration.getInstance(getProject()).addResourceFilePattern("*.ASTTransformation");

    myFixture.addFileToProject("META-INF/services/org.codehaus.groovy.transform.ASTTransformation", "Transf");
  }

  public void testApplyTransformsFromDependencies() throws Exception {
    addTransform();

    myFixture.addFileToProject("dependent/Bar.groovy", "class Bar {\n" +
                                                       "  static Object zzz = autoImported()\n" +
                                                       "  static void main(String[] args) {\n" +
                                                       "    println zzz\n" +
                                                       "  }\n" +
                                                       "}");

    myFixture.addFileToProject("dependent/AJavaClass.java", "class AJavaClass {}");

    Module dep = addDependentModule();

    addGroovyLibrary(dep);

    assertEmpty(make());
    assertOutput("Bar", "239", dep);
  }

  public void testIndirectDependencies() throws Exception {
    myFixture.addFileToProject("dependent1/Bar1.groovy", "class Bar1 {}");
    myFixture.addFileToProject("dependent2/Bar2.groovy", "class Bar2 extends Bar1 {}");
    PsiFile main = myFixture.addFileToProject("Main.groovy", "class Main extends Bar2 {}");

    Module dep1 = addModule('dependent1', true)
    Module dep2 = addModule('dependent2', true)
    ModuleRootModificationUtil.addDependency dep2, dep1
    ModuleRootModificationUtil.addDependency myModule, dep2

    addGroovyLibrary(dep1);
    addGroovyLibrary(dep2);

    assertEmpty(make())

    touch(main.virtualFile)
    assertEmpty(make())
  }

  public void testExtendFromGroovyAbstractClass() throws Exception {
    myFixture.addFileToProject "Super.groovy", "abstract class Super {}"
    myFixture.addFileToProject "AJava.java", "public class AJava {}"
    assertEmpty make()

    myFixture.addFileToProject "Sub.groovy", "class Sub extends Super {}"
    assertEmpty make()
  }

  public void test1_7InnerClass() throws Exception {
    myFixture.addFileToProject "Foo.groovy", """
class Foo {
  static class Bar {}
}"""
    def javaFile = myFixture.addFileToProject("AJava.java", "public class AJava extends Foo.Bar {}")
    assertEmpty make()

    touch(javaFile.virtualFile)
    assertEmpty make()
  }

  public void testRecompileDependentClass() throws Exception {
    def cloud = myFixture.addFileToProject("Cloud.groovy", """
class Cloud {
  def accessFooProperty(Foo c) {
    c.prop = 2
  }
}
""")
    myFixture.addFileToProject "Foo.groovy", """
class Foo {
  def withGooParameter(Goo x) {}
}"""
    def goo = myFixture.addFileToProject("Goo.groovy", "class Goo {}")

    assertEmpty make()

    touch(cloud.virtualFile)
    touch(goo.virtualFile)
    assertEmpty make()
  }

  public void testRecompileExpressionReferences() throws Exception {
    def rusCon = myFixture.addFileToProject('RusCon.groovy', '''
interface RusCon {
  Closure foo = { Seq.foo() }
}''')
    myFixture.addFileToProject "Seq.groovy", """
class Seq implements RusCon {
  static def foo() { }
}"""
    assertEmpty make()

    touch(rusCon.virtualFile)
    assertEmpty make()
  }

  public void testRecompileImportedClass() throws Exception {
    def bar = myFixture.addFileToProject("pack/Bar.groovy", """
package pack
import pack.Foo
class Bar {}
""")
    myFixture.addFileToProject "pack/Foo.groovy", """
package pack
class Foo extends Goo {
}"""
    def goo = myFixture.addFileToProject("pack/Goo.groovy", """
package pack
class Goo {}""")

    assertEmpty make()

    touch(bar.virtualFile)
    touch(goo.virtualFile)
    assertEmpty make()
  }

  public void testRecompileDependentClassesWithOnlyOneChanged() throws Exception {
    def bar = myFixture.addFileToProject("Bar.groovy", """
class Bar {
  Foo f
}
""")
    myFixture.addFileToProject "Foo.groovy", """
class Foo extends Bar {
}"""

    assertEmpty make()

    touch(bar.virtualFile)
    assertEmpty make()
  }

  public void testDollarGroovyInnerClassUsagesInStubs() throws Exception {
    def javaFile = myFixture.addClass("""
      public class JavaClass {
        public static class InnerJavaClass {}
      }
""")
    myFixture.addFileToProject("WithInner.groovy", """
class WithInner {
  static class Inner {}
}
""")
    assertEmpty make()

    myFixture.addFileToProject("Usage.groovy", """
class Usage {
  def foo(WithInner.Inner i) {}
  def foo(JavaClass.InnerJavaClass i) {}
}
""")

    touch(javaFile.containingFile.virtualFile)
    assertEmpty make()
  }

  public void testDollarGroovyInnerClassUsagesInStubs2() throws Exception {
    myFixture.addClass(""" public class JavaClass { } """)
    myFixture.addFileToProject("WithInner.groovy", """
class WithInner {
  static class Inner {}
}
""")

    myFixture.addFileToProject("Usage.groovy", """
class Usage {
  def foo(WithInner.Inner i) {}
}
""")

    assertEmpty make()
  }

  public void testGroovyAnnotations() {
    myFixture.addClass 'public @interface Anno { Class[] value(); }'
    myFixture.addFileToProject 'Foo.groovy', '@Anno([String]) class Foo {}'
    myFixture.addFileToProject 'Bar.java', 'class Bar extends Foo {}'

    assertEmpty make()
  }

  public void testGenericStubs() {
    myFixture.addFileToProject 'Foo.groovy', 'class Foo { List<String> list }'
    myFixture.addFileToProject 'Bar.java', 'class Bar {{ for (String s : new Foo().getList()) {} }}'
    assertEmpty make()
  }

  public void testDuplicateClassDuringCompilation() throws Exception {
    def base = myFixture.addFileToProject('p/Base.groovy', 'package p; class Base { }').virtualFile
    myFixture.addFileToProject('p/Indirect.groovy', '''package p
class Indirect {
  private static class Inner { Base b }

  private Indirect.Inner foo(Indirect.Inner g1, Inner g2, Base b) {}
 }''').virtualFile
    def foo = myFixture.addFileToProject('Foo.groovy', 'class Foo { p.Indirect foo() {} }').virtualFile
    assertEmpty make()

    touch(foo)
    touch(base)
    assertEmpty make()
  }

  public void testDontRecompileUnneeded() {
    myFixture.addFileToProject('Base.groovy', 'class Base { }')
    def foo = myFixture.addFileToProject('Foo.groovy', 'class Foo extends Base { }').virtualFile
    myFixture.addFileToProject('Bar.groovy', 'class Bar extends Foo { }')
    def main = myFixture.addFileToProject('Main.groovy', 'class Main extends Bar { }').virtualFile
    assertEmpty make()
    long oldBaseStamp = findClassFile("Base").timeStamp
    long oldMainStamp = findClassFile("Main").timeStamp

    touch(main)
    touch(foo)
    assertEmpty make()
    assert oldMainStamp != findClassFile("Main").timeStamp
    assert oldBaseStamp == findClassFile("Base").timeStamp
  }

  public void testPartialCrossRecompile() {
    def used = myFixture.addFileToProject('Used.groovy', 'class Used { }')
    def java = myFixture.addFileToProject('Java.java', 'class Java { void foo(Used used) {} }')
    def main = myFixture.addFileToProject('Main.groovy', 'class Main extends Java {  }').virtualFile

    assertEmpty compileModule(myModule)

    touch(used.virtualFile)
    touch(main)
    assertEmpty make()

    assertEmpty compileModule(myModule)
    assertEmpty compileModule(myModule)

    setFileText(used, 'class Used2 {}')
    shouldFail { make() }
    assert findClassFile('Used') == null

    setFileText(used, 'class Used3 {}')
    setFileText(java, 'class Java { void foo(Used3 used) {} }')
    assertEmpty make()

    assert findClassFile('Used2') == null
  }

  public void testClassLoadingDuringBytecodeGeneration() {
    def used = myFixture.addFileToProject('Used.groovy', 'class Used { }')
    def java = myFixture.addFileToProject('Java.java', '''
abstract class Java {
  Object getProp() { return null; }
  abstract void foo(Used used);
}''')
    def main = myFixture.addFileToProject('Main.groovy', '''
class Main {
  def foo(Java j) {
    return j.prop
  }
}''').virtualFile

    assertEmpty make()

    touch(used.virtualFile)
    touch(main)
    assertEmpty make()
  }

  public void testMakeInDependentModuleAfterChunkRebuild() {
    def used = myFixture.addFileToProject('Used.groovy', 'class Used { }')
    def java = myFixture.addFileToProject('Java.java', 'class Java { void foo(Used used) {} }')
    def main = myFixture.addFileToProject('Main.groovy', 'class Main extends Java {  }').virtualFile

    addGroovyLibrary(addDependentModule())

    def dep = myFixture.addFileToProject("dependent/Dep.java", "class Dep { }")

    assertEmpty make()

    setFileText(used, 'class Used { String prop }')
    touch(main)
    setFileText(dep, 'class Dep { String prop = new Used().getProp(); }')

    assertEmpty make()
  }

  public void "test module cycle"() {
    def dep = addDependentModule()
    ModuleRootModificationUtil.addDependency(myModule, dep)
    addGroovyLibrary(dep)

    myFixture.addFileToProject('Foo.groovy', 'class Foo extends Bar { static void main(String[] args) { println "Hello from Foo" } }')
    myFixture.addFileToProject('FooX.java', 'class FooX extends Bar { }')
    myFixture.addFileToProject("dependent/Bar.groovy", "class Bar { Foo f; static void main(String[] args) { println 'Hello from Bar' } }")
    myFixture.addFileToProject("dependent/BarX.java", "class BarX { Foo f; }")

    def checkClassFiles = {
      assert findClassFile('Foo', myModule)
      assert findClassFile('FooX', myModule)
      assert findClassFile('Bar', dep)
      assert findClassFile('BarX', dep)

      assert !findClassFile('Bar', myModule)
      assert !findClassFile('BarX', myModule)
      assert !findClassFile('Foo', dep)
      assert !findClassFile('FooX', dep)
    }

    assertEmpty(make())
    checkClassFiles()

    assertEmpty(make())
    checkClassFiles()

    assertOutput('Foo', 'Hello from Foo', myModule)
    assertOutput('Bar', 'Hello from Bar', dep)

    checkClassFiles()
  }

  public void testCompileTimeConstants() {
    myFixture.addFileToProject 'Gr.groovy', '''
interface Gr {
  String HELLO = "Hello"
  int MAGIC = 239
  Boolean BOOL = true
  boolean bool = true
}'''
    myFixture.addFileToProject 'Main.java', '''
public class Main {
  public static void main(String[] args) {
    System.out.println(Gr.HELLO + ", " + Gr.BOOL + Gr.bool + Gr.MAGIC);
  }
}
'''
    make()
    assertOutput 'Main', 'Hello, truetrue239'
  }

  public void "test reporting rebuild errors caused by missing files excluded from compilation"() {
    def foo = myFixture.addFileToProject('Foo.groovy', 'class Foo {}')
    myFixture.addFileToProject 'Bar.groovy', 'class Bar extends Foo {}'

    make()

    excludeFromCompilation(foo)

    shouldFail { rebuild() }
  }

  private void excludeFromCompilation(PsiFile foo) {
    final ExcludedEntriesConfiguration configuration =
      ((CompilerConfigurationImpl)CompilerConfiguration.getInstance(project)).getExcludedEntriesConfiguration()
    configuration.addExcludeEntryDescription(new ExcludeEntryDescription(foo.virtualFile, false, true, testRootDisposable))
  }

  public void "test make stub-level error and correct it"() {
    def foo = myFixture.addFileToProject('Foo.groovy', 'class Foo { }')
    myFixture.addFileToProject('Bar.java', 'class Bar extends Foo {}')

    assertEmpty make()

    setFileText(foo, 'class Foo implements Runnabl {}')

    shouldFail { make() }

    setFileText(foo, 'class Foo {}')

    assertEmpty make()
  }

  //todo jeka: when recompiling module, delete all class files including those with excluded source
  public void "_test reporting module compile errors caused by missing files excluded from compilation"() {
    def foo = myFixture.addFileToProject('Foo.groovy', 'class Foo {}')
    myFixture.addFileToProject('Bar.groovy', 'class Bar extends Foo {}')

    make()

    excludeFromCompilation(foo)

    shouldFail { compileModule(myModule) }
  }

  public void "test stubs generated while processing groovy class file dependencies"() {
    def foo = myFixture.addFileToProject('Foo.groovy', 'class Foo { }')
    def bar = myFixture.addFileToProject('Bar.groovy', 'class Bar extends Foo { }')
    def client = myFixture.addFileToProject('Client.groovy', 'class Client { Bar bar = new Bar() }')
    def java = myFixture.addFileToProject('Java.java', 'class Java extends Client { String getName(Bar bar) { return bar.toString();  } }')

    assertEmpty(make())

    setFileText(bar, 'class Bar { }')

    assertEmpty(make())
    assert findClassFile("Client")
  }

  public void "test navigate from stub to source"() {
    GroovyFile groovyFile = (GroovyFile) myFixture.addFileToProject("a.groovy", "class Groovy3 { InvalidType type }")
    myFixture.addClass("class Java4 extends Groovy3 {}").containingFile

    def msg = make().find { it.message.contains('InvalidType') }
    assert msg?.virtualFile
    ApplicationManager.application.runWriteAction { msg.virtualFile.delete(this) }

    def messages = make()
    assert messages
    def error = messages.find { it.message.contains('InvalidType') }
    assert error?.virtualFile
    assert groovyFile.classes[0] == GroovyCompilerLoader.findClassByStub(project, error.virtualFile)

  }

  public void "test ignore groovy internal non-existent interface helper inner class"() {
    myFixture.addFileToProject 'Foo.groovy', '''
interface Foo {}

class Zoo {
  Foo foo() {}
  static class Inner implements Foo {}
}

'''
    def bar = myFixture.addFileToProject('Bar.groovy', 'class Bar { def foo = new Zoo.Inner() {}  }')

    assertEmpty make()
    assertEmpty compileFiles(bar.virtualFile)
  }

  public void "test multiline strings"() {
    myFixture.addFileToProject 'Foo.groovy', '''class Foo {
  public static final String s = """
multi
line
string
"""
 } '''
    myFixture.addFileToProject 'Bar.java', 'class Bar extends Foo {} '

    assertEmpty make()
  }

  public void "test inner java class references with incremental recompilation"() {
    def bar1 = myFixture.addFileToProject('bar/Bar1.groovy', 'package bar; class Bar1 extends Bar2 { } ')
    myFixture.addFileToProject('bar/Bar2.java', 'package bar; class Bar2 extends Bar3 { } ')
    def bar3 = myFixture.addFileToProject('bar/Bar3.groovy', 'package bar; class Bar3 { Bar1 property } ')

    myFixture.addClass("package foo; public class Outer { public static class Inner extends bar.Bar1 { } }")
    def using = myFixture.addFileToProject('UsingInner.groovy', 'import foo.Outer; class UsingInner extends bar.Bar1 { Outer.Inner property } ')

    assertEmpty make()

    touch bar1.virtualFile
    touch bar3.virtualFile
    touch using.virtualFile

    assertEmpty make()
  }

  public void "test rename class to java and touch its usage"() {
    def usage = myFixture.addFileToProject('Usage.groovy', 'class Usage { Renamed r } ')
    def renamed = myFixture.addFileToProject('Renamed.groovy', 'public class Renamed { } ')
    assertEmpty make()

    touch usage.virtualFile
    setFileName(renamed, 'Renamed.java')
    assertEmpty make()
  }

  public void "test compiling static extension"() {
    setupTestSources()
    myFixture.addFileToProject "src/extension/Extension.groovy", """
package extension
import groovy.transform.CompileStatic

@CompileStatic class Extension {
    static <T> T test2(List<T> self) {
        self.first()
    }
}"""
    myFixture.addFileToProject "src/META-INF/services/org.codehaus.groovy.runtime.ExtensionModule", """
moduleName=extension-verify
moduleVersion=1.0-test
extensionClasses=extension.Extension
staticExtensionClasses=
"""
    myFixture.addFileToProject "tests/AppTest.groovy", """
class AppTest {
    @groovy.transform.CompileStatic
    static main(args) {
        List<String> list = new ArrayList<>()
        list.add("b")
        list.add("c")
        println list.test2()
    }
}
"""
    assertEmpty make()
    assertOutput 'AppTest', 'b'
  }

  public void "test no groovy library"() {
    myFixture.addFileToProject("dependent/a.groovy", "");
    addModule("dependent", true)

    def messages = make()
    assert messages.find { it.message.contains("Cannot compile Groovy files: no Groovy library is defined for module 'dependent'") }
  }

  public void testGroovyOutputIsInstrumented() {
    myFixture.addFileToProject("Bar.groovy",
       "import org.jetbrains.annotations.NotNull; " +
       "public class Bar {" +
         "void xxx(@NotNull String param) { println param }\n" +
         "static void main(String[] args) { new Bar().xxx(null) }"+
       "}"
    );

    File annotations = new File(PathManager.getJarPathForClass(NotNull.class));
    PsiTestUtil.addLibrary(myModule, "annotations", annotations.getParent(), annotations.getName());

    assertEmpty(make());

    final Ref<Boolean> exceptionFound = Ref.create(Boolean.FALSE);
    ProcessHandler process = runProcess("Bar", myModule, DefaultRunExecutor.class, new ProcessAdapter() {
      @Override
      public void onTextAvailable(ProcessEvent event, Key outputType) {
        if (ProcessOutputTypes.SYSTEM != outputType) {
          if (!exceptionFound.get()) {
            exceptionFound.set(event.getText().contains("java.lang.IllegalArgumentException: Argument for @NotNull parameter 'param' of Bar.xxx must not be null"));
          }
        }
      }
    }, ProgramRunner.PROGRAM_RUNNER_EP.findExtension(DefaultJavaProgramRunner.class));
    process.waitFor();

    assertTrue(exceptionFound.get());
  }

}