Variables testing: confirm space-containing values
[scons.git] / SCons / Tool / JavaCommonTests.py
blobbb5c57f036015a4574ab75765383202d724e11e4
1 # MIT License
3 # Copyright The SCons Foundation
5 # Permission is hereby granted, free of charge, to any person obtaining
6 # a copy of this software and associated documentation files (the
7 # "Software"), to deal in the Software without restriction, including
8 # without limitation the rights to use, copy, modify, merge, publish,
9 # distribute, sublicense, and/or sell copies of the Software, and to
10 # permit persons to whom the Software is furnished to do so, subject to
11 # the following conditions:
13 # The above copyright notice and this permission notice shall be included
14 # in all copies or substantial portions of the Software.
16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
17 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
18 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 import os.path
25 import unittest
26 import fnmatch
28 import SCons.Scanner.IDL
29 import SCons.Tool.JavaCommon
31 import TestSCons
33 # Adding trace=trace to any of the parse_jave() calls below will cause
34 # the parser to spit out trace messages of the tokens it sees and the
35 # attendant transitions.
37 def trace(token, newstate) -> None:
38 from SCons.Debug import Trace
39 statename = newstate.__class__.__name__
40 Trace('token = %s, state = %s\n' % (repr(token), statename))
42 class parse_javaTestCase(unittest.TestCase):
44 def test_bare_bones(self) -> None:
45 """Test a bare-bones class"""
47 input = """\
48 package com.sub.bar;
50 public class Foo
53 public static void main(String[] args)
56 /* This tests a former bug where strings would eat later code. */
57 String hello1 = new String("Hello, world!");
62 """
63 pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input)
64 assert pkg_dir == os.path.join('com', 'sub', 'bar'), pkg_dir
65 assert classes == ['Foo'], classes
68 def test_file_parser(self) -> None:
69 """Test the file parser"""
70 input = """\
71 package com.sub.bar;
73 public class Foo
75 public static void main(String[] args)
77 /* This tests that unicode is handled . */
78 String hello1 = new String("ఎత్తువెడల్పు");
79 /* and even smart quotes “like this” ‘and this’ */
82 """
83 file_name = 'test_file_parser.java'
84 with open(file_name, 'w', encoding='UTF-8') as jf:
85 print(input, file=jf)
87 pkg_dir, classes = SCons.Tool.JavaCommon.parse_java_file(file_name)
88 if os.path.exists(file_name):
89 os.remove(file_name)
90 assert pkg_dir == os.path.join('com', 'sub', 'bar'), pkg_dir
91 assert classes == ['Foo'], classes
94 def test_dollar_sign(self) -> None:
95 """Test class names with $ in them"""
97 input = """\
98 public class BadDep {
99 public void new$rand () {}
102 pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input)
103 assert pkg_dir is None, pkg_dir
104 assert classes == ['BadDep'], classes
108 def test_inner_classes(self) -> None:
109 """Test parsing various forms of inner classes"""
111 input = """\
112 class Empty {
115 interface Listener {
116 public void execute();
119 public
120 class
121 Test implements Listener {
122 class Inner {
123 void go() {
124 use(new Listener() {
125 public void execute() {
126 System.out.println("In Inner");
130 String s1 = "class A";
131 String s2 = "new Listener() { }";
132 /* class B */
133 /* new Listener() { } */
136 class Inner2 {
137 Inner2() { Listener l = new Listener(); }
140 /* Make sure this class doesn't get interpreted as an inner class of the previous one, when "new" is used in the previous class. */
141 class Inner3 {
145 public static void main(String[] args) {
146 new Test().run();
149 void run() {
150 use(new Listener() {
151 public void execute() {
152 use(new Listener( ) {
153 public void execute() {
154 System.out.println("Inside execute()");
160 new Inner().go();
163 void use(Listener l) {
164 l.execute();
168 class Private {
169 void run() {
170 new Listener() {
171 public void execute() {
178 pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input, '1.4')
179 assert pkg_dir is None, pkg_dir
180 expect = [
181 'Empty',
182 'Listener',
183 'Test$1',
184 'Test$Inner',
185 'Test$Inner2',
186 'Test$Inner3',
187 'Test$2',
188 'Test$3',
189 'Test',
190 'Private$1',
191 'Private',
193 assert classes == expect, classes
195 pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input, '1.5')
196 assert pkg_dir is None, pkg_dir
197 expect = [
198 'Empty',
199 'Listener',
200 'Test$Inner$1',
201 'Test$Inner',
202 'Test$Inner2',
203 'Test$Inner3',
204 'Test$1',
205 'Test$1$1',
206 'Test',
207 'Private$1',
208 'Private',
210 assert classes == expect, (expect, classes)
212 pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input, '5')
213 assert pkg_dir is None, pkg_dir
214 expect = [
215 'Empty',
216 'Listener',
217 'Test$Inner$1',
218 'Test$Inner',
219 'Test$Inner2',
220 'Test$Inner3',
221 'Test$1',
222 'Test$1$1',
223 'Test',
224 'Private$1',
225 'Private',
227 assert classes == expect, (expect, classes)
231 def test_comments(self) -> None:
232 """Test a class with comments"""
234 input = """\
235 package com.sub.foo;
237 import java.rmi.Naming;
238 import java.rmi.RemoteException;
239 import java.rmi.RMISecurityManager;
240 import java.rmi.server.UnicastRemoteObject;
242 public class Example1 extends UnicastRemoteObject implements Hello {
244 public Example1() throws RemoteException {
245 super();
248 public String sayHello() {
249 return "Hello World!";
252 public static void main(String args[]) {
253 if (System.getSecurityManager() == null) {
254 System.setSecurityManager(new RMISecurityManager());
256 // a comment
257 try {
258 Example1 obj = new Example1();
260 Naming.rebind("//myhost/HelloServer", obj);
262 System.out.println("HelloServer bound in registry");
263 } catch (Exception e) {
264 System.out.println("Example1 err: " + e.getMessage());
265 e.printStackTrace();
271 pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input)
272 assert pkg_dir == os.path.join('com', 'sub', 'foo'), pkg_dir
273 assert classes == ['Example1'], classes
276 def test_arrays(self) -> None:
277 """Test arrays of class instances"""
279 input = """\
280 public class Test {
281 MyClass abc = new MyClass();
282 MyClass xyz = new MyClass();
283 MyClass _array[] = new MyClass[] {
284 abc,
290 pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input)
291 assert pkg_dir is None, pkg_dir
292 assert classes == ['Test'], classes
296 def test_backslash(self) -> None:
297 """Test backslash handling"""
299 input = """\
300 public class MyTabs
302 private class MyInternal
305 private final static String PATH = "images\\\\";
309 pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input)
310 assert pkg_dir is None, pkg_dir
311 assert classes == ['MyTabs$MyInternal', 'MyTabs'], classes
314 def test_enum(self) -> None:
315 """Test the Java 1.5 enum keyword"""
317 input = """\
318 package p;
319 public enum a {}
322 pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input)
323 assert pkg_dir == 'p', pkg_dir
324 assert classes == ['a'], classes
327 def test_anon_classes(self) -> None:
328 """Test anonymous classes"""
330 input = """\
331 public abstract class TestClass
333 public void completed()
335 new Thread()
337 }.start();
339 new Thread()
341 }.start();
346 pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input)
347 assert pkg_dir is None, pkg_dir
348 assert classes == ['TestClass$1', 'TestClass$2', 'TestClass'], classes
351 def test_closing_bracket(self) -> None:
352 """Test finding a closing bracket instead of an anonymous class"""
354 input = """\
355 class TestSCons {
356 public static void main(String[] args) {
357 Foo[] fooArray = new Foo[] { new Foo() };
361 class Foo { }
364 pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input)
365 assert pkg_dir is None, pkg_dir
366 assert classes == ['TestSCons', 'Foo'], classes
369 def test_dot_class_attributes(self) -> None:
370 """Test handling ".class" attributes"""
372 input = """\
373 public class Test extends Object
375 static {
376 Class c = Object[].class;
377 Object[] s = new Object[] {};
382 pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input)
383 assert classes == ['Test'], classes
385 input = """\
386 public class A {
387 public class B {
388 public void F(Object[] o) {
389 F(new Object[] {Object[].class});
391 public void G(Object[] o) {
392 F(new Object[] {});
398 pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input)
399 assert pkg_dir is None, pkg_dir
400 assert classes == ['A$B', 'A'], classes
402 def test_anonymous_classes_with_parentheses(self) -> None:
403 """Test finding anonymous classes marked by parentheses"""
405 input = """\
406 import java.io.File;
408 public class Foo {
409 public static void main(String[] args) {
410 File f = new File(
411 new File("a") {
412 public String toString() {
413 return "b";
415 } to String()
417 public String toString() {
418 return "c";
425 pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input, '1.4')
426 assert classes == ['Foo$1', 'Foo$2', 'Foo'], classes
428 pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input, '1.5')
429 assert classes == ['Foo$1', 'Foo$1$1', 'Foo'], classes
431 pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input, '6')
432 assert classes == ['Foo$1', 'Foo$1$1', 'Foo'], classes
436 def test_nested_anonymous_inner_classes(self) -> None:
437 """Test finding nested anonymous inner classes"""
439 input = """\
440 // import java.util.*;
442 public class NestedExample
444 public NestedExample()
446 Thread t = new Thread() {
447 public void start()
449 Thread t = new Thread() {
450 public void start()
452 try {Thread.sleep(200);}
453 catch (Exception e) {}
456 while (true)
458 try {Thread.sleep(200);}
459 catch (Exception e) {}
466 public static void main(String argv[])
468 NestedExample e = new NestedExample();
473 pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input, '1.4')
474 expect = [ 'NestedExample$1', 'NestedExample$2', 'NestedExample' ]
475 assert expect == classes, (expect, classes)
477 pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input, '1.5')
478 expect = [ 'NestedExample$1', 'NestedExample$1$1', 'NestedExample' ]
479 assert expect == classes, (expect, classes)
481 pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input, '6')
482 expect = [ 'NestedExample$1', 'NestedExample$1$1', 'NestedExample' ]
483 assert expect == classes, (expect, classes)
485 def test_lambda_after_new(self) -> None:
486 """Test lamdas after new"""
488 input = """\
489 // import java.util.*;
491 public class LamdaExample
494 public void testFunc (int arg1, String arg2, Runnable lambda){
496 public LamdaExample()
498 testFunc(
500 new String("test"),
501 // Lambda symbol is after new, and used curly braces so
502 // we should not parse this as a new class.
503 () -> {}
508 public static void main(String argv[])
510 LamdaExample e = new LamdaExample();
514 pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input, '1.4')
515 expect = [ 'LamdaExample' ]
516 assert expect == classes, (expect, classes)
518 pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input, '1.8')
519 expect = [ 'LamdaExample' ]
520 assert expect == classes, (expect, classes)
522 def test_private_inner_class_instantiation(self) -> None:
523 """Test anonymous inner class generated by private instantiation"""
525 input = """\
526 class test
528 test()
530 super();
531 new inner();
534 static class inner
536 private inner() {}
541 # This is what we *should* generate, apparently due to the
542 # private instantiation of the inner class, but don't today.
543 #expect = [ 'test$1', 'test$inner', 'test' ]
545 # What our parser currently generates, which doesn't match
546 # what the Java compiler actually generates.
547 expect = [ 'test$inner', 'test' ]
549 pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input, '1.4')
550 assert expect == classes, (expect, classes)
552 pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input, '1.5')
553 assert expect == classes, (expect, classes)
555 def test_floating_point_numbers(self) -> None:
556 """Test floating-point numbers in the input stream"""
557 input = """
558 // Broken.java
559 class Broken
562 * Detected.
564 Object anonymousInnerOK = new Runnable() { public void run () {} };
567 * Detected.
569 class InnerOK { InnerOK () { } }
572 System.out.println("a number: " + 1000.0 + "");
576 * Not detected.
578 Object anonymousInnerBAD = new Runnable() { public void run () {} };
581 * Not detected.
583 class InnerBAD { InnerBAD () { } }
587 expect = ['Broken$1', 'Broken$InnerOK', 'Broken$2', 'Broken$InnerBAD', 'Broken']
589 pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input, '1.4')
590 assert expect == classes, (expect, classes)
592 pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input, '1.5')
593 assert expect == classes, (expect, classes)
596 def test_genercis(self) -> None:
597 """Test that generics don't interfere with detecting anonymous classes"""
599 input = """\
600 import java.util.Date;
601 import java.util.Comparator;
603 public class Foo
605 public void foo()
607 Comparator<Date> comp = new Comparator<Date>()
609 static final long serialVersionUID = 1L;
610 public int compare(Date lhs, Date rhs)
612 return 0;
619 expect = [ 'Foo$1', 'Foo' ]
621 pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input, '1.6')
622 assert expect == classes, (expect, classes)
624 pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input, '6')
625 assert expect == classes, (expect, classes)
628 def test_in_function_class_declaration(self) -> None:
630 Test that implementing a class in a function call doesn't confuse SCons.
633 input = """
634 package com.Matthew;
636 public class AnonDemo {
638 public static void main(String[] args) {
639 new AnonDemo().execute();
642 public void execute() {
643 Foo bar = new Foo(new Foo() {
644 @Override
645 public int getX() { return this.x; }
646 }) {
647 @Override
648 public int getX() { return this.x; }
652 public abstract class Foo {
653 public int x;
654 public abstract int getX();
656 public Foo(Foo f) {
657 this.x = f.x;
660 public Foo() {}
664 expect = ['AnonDemo$1',
665 'AnonDemo$2',
666 'AnonDemo$Foo',
667 'AnonDemo']
668 pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input, '1.8')
669 assert expect == classes, (expect, classes)
671 def test_jdk_globs(self) -> None:
673 Verify that the java path globs work with specific examples.
674 :return:
676 from SCons.Tool.JavaCommon import (
677 java_linux_include_dirs_glob,
678 java_linux_version_include_dirs_glob,
679 java_win32_dir_glob,
680 java_win32_version_dir_glob,
681 java_macos_include_dir_glob,
682 java_macos_version_include_dir_glob,
685 # Test windows globs
686 win_java_dirs = [
687 (r'C:/Program Files/Java/jdk1.8.0_201/bin', '1.8.0'),
688 (r'C:/Program Files/Java/jdk-11.0.2/bin', '11.0.2'),
689 (r'C:/Program Files/AdoptOpenJDK/jdk-16.0.1.9-hotspot/bin', '16.0.1'),
690 (r'C:/Program Files/Microsoft/jdk-17.0.0.35-hotspot/bin', '17.0.0'),
691 (r'C:/Program Files/OpenJDK/openjdk-11.0.13_8/bin', '11.0.13'),
692 (r'C:/Program Files/RedHat/java-1.8.0-openjdk-1.8.0.312-1/bin', '1.8.0'),
693 (r'C:/Program Files/RedHat/java-11-openjdk-11.0.13-1/bin', '11.0.13'),
696 for (wjd, version) in win_java_dirs:
697 if not fnmatch.fnmatch(wjd, java_win32_dir_glob):
698 self.fail(
699 "Didn't properly match %s with pattern %s"
700 % (wjd, java_win32_dir_glob)
702 if not fnmatch.fnmatch(wjd, java_win32_version_dir_glob % version):
703 self.fail(
704 "Didn't properly match %s with version (%s) specific pattern %s"
705 % (wjd, version, java_win32_version_dir_glob % version)
708 non_win_java_include_dirs = [
710 '/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.191.b12-0.el7_5.x86_64/include',
711 '1.8.0',
713 ('/usr/lib/jvm/java-1.8.0-openjdk-amd64/include', '1.8.0'),
714 ('/usr/lib/jvm/java-8-openjdk-amd64/include', '8'),
717 # Test non-windows/non-macos globs
718 for (wjd, version) in non_win_java_include_dirs:
719 match = False
720 globs_tried = []
721 for jlig in java_linux_include_dirs_glob:
722 globs_tried.append(jlig)
724 if fnmatch.fnmatch(wjd, jlig):
725 match = True
726 break
728 if not match:
729 self.fail(
730 "Didn't properly match %s with pattern %s" % (wjd, globs_tried)
733 match = False
734 globs_tried = []
735 for jlvig in java_linux_version_include_dirs_glob:
736 globs_tried.append(jlvig % version)
737 if fnmatch.fnmatch(wjd, jlvig % version):
738 match = True
739 break
741 if not match:
742 self.fail(
743 "Didn't properly match %s with version (%s) specific pattern %s"
744 % (wjd, version, globs_tried)
747 # Test macos globs
748 # Test windows globs
749 macos_java_dirs = [
750 # ('/System/Library/Frameworks/JavaVM.framework/Headers/', None),
752 '/System/Library/Frameworks/JavaVM.framework/Versions/11.0.2/Headers/',
753 '11.0.2',
757 if not fnmatch.fnmatch(
758 '/System/Library/Frameworks/JavaVM.framework/Headers/',
759 java_macos_include_dir_glob,
761 self.fail(
762 "Didn't properly match %s with pattern %s"
764 '/System/Library/Frameworks/JavaVM.framework/Headers/',
765 java_macos_include_dir_glob,
769 for (wjd, version) in macos_java_dirs:
770 if not fnmatch.fnmatch(wjd, java_macos_version_include_dir_glob % version):
771 self.fail(
772 "Didn't properly match %s with version (%s) specific pattern %s"
773 % (wjd, version, java_macos_version_include_dir_glob % version)
777 if __name__ == "__main__":
778 unittest.main()
780 # Local Variables:
781 # tab-width:4
782 # indent-tabs-mode:nil
783 # End:
784 # vim: set expandtab tabstop=4 shiftwidth=4: