From 1025cde05abe95be6ca65aab3ba126258efc647f Mon Sep 17 00:00:00 2001 From: wisberg Date: Mon, 16 Dec 2002 18:14:27 +0000 Subject: [PATCH] initial version --- lib/commons/commons-src.zip | Bin 0 -> 300608 bytes lib/commons/commons.jar | Bin 0 -> 286777 bytes lib/readme-lib-module.html | 5 + testing-client/.classpath | 10 + testing-client/.project | 19 + testing-client/src/.cvsignore | 1 + .../src/org/aspectj/testing/Tester.java | 983 +++++++++ .../testsrc/TestingClientModuleTests.java | 31 + testing-util/.classpath | 11 + testing-util/.project | 19 + testing-util/TestUtilTest.launch | 38 + testing-util/src/.cvsignore | 1 + .../org/aspectj/testing/util/TestUtil.java | 616 ++++++ .../testing/util/TestCompareClassFile$1.class | Bin 0 -> 463 bytes .../testing/util/TestCompareClassFile.class | Bin 0 -> 4094 bytes .../differentFile/actual/TestUtilTest.java | 95 + .../differentFile/expected/TestUtilTest.java | 94 + .../sameFile/actual/TestUtilTest.java | 94 + .../sameFile/expected/TestUtilTest.java | 94 + .../testsrc/TestingUtilModuleTests.java | 32 + .../testing/util/TestCompareClassFile.java | 148 ++ .../aspectj/testing/util/TestUtilTest.java | 116 + .../org/aspectj/testing/util/UtilTests.java | 32 + testing/.classpath | 18 + testing/.project | 21 + testing/src/.cvsignore | 1 + .../internal/tools/ant/taskdefs/Ajctest.java | 1864 +++++++++++++++++ .../tools/ant/taskdefs/MainWrapper.java | 181 ++ .../testing/gui/resources/gui.properties | 53 + .../testing/gui/resources/images/Back16.gif | Bin 0 -> 183 bytes .../testing/gui/resources/images/Delete24.gif | Bin 0 -> 93 bytes .../gui/resources/images/Forward16.gif | Bin 0 -> 183 bytes .../testing/gui/resources/images/Open16.gif | Bin 0 -> 138 bytes .../testing/gui/resources/images/Open24.gif | Bin 0 -> 230 bytes .../testing/gui/resources/images/Quit24.gif | Bin 0 -> 313 bytes .../testing/gui/resources/images/Reload24.gif | Bin 0 -> 245 bytes .../testing/gui/resources/images/Save16.gif | Bin 0 -> 206 bytes .../testing/gui/resources/images/Save24.gif | Bin 0 -> 80 bytes .../testing/gui/resources/images/SaveAs16.gif | Bin 0 -> 255 bytes .../testing/gui/resources/images/SaveAs24.gif | Bin 0 -> 160 bytes .../testing/gui/resources/images/Start16.gif | Bin 0 -> 140 bytes .../testing/gui/resources/images/Start24.gif | Bin 0 -> 165 bytes .../testing/gui/resources/images/Stop16.gif | Bin 0 -> 171 bytes .../testing/gui/resources/images/Stop24.gif | Bin 0 -> 488 bytes .../testing/gui/resources/images/Test16.gif | Bin 0 -> 933 bytes .../gui/resources/images/Testfailed16.gif | Bin 0 -> 872 bytes .../gui/resources/images/Testopen16.gif | Bin 0 -> 935 bytes .../gui/resources/images/Testpassed16.gif | Bin 0 -> 230 bytes .../harness/bridge/AbstractRunSpec.java | 883 ++++++++ .../harness/bridge/AjcMessageHandler.java | 282 +++ .../testing/harness/bridge/AjcTest.java | 371 ++++ .../testing/harness/bridge/CompilerRun.java | 966 +++++++++ .../testing/harness/bridge/DirChanges.java | 522 +++++ .../harness/bridge/FlatSuiteReader.java | 377 ++++ .../testing/harness/bridge/Globals.java | 94 + .../testing/harness/bridge/IAjcRun.java | 44 + .../testing/harness/bridge/IRunSpec.java | 24 + .../harness/bridge/IncCompilerRun.java | 444 ++++ .../testing/harness/bridge/JavaRun.java | 298 +++ .../harness/bridge/RunSpecIterator.java | 256 +++ .../testing/harness/bridge/Sandbox.java | 386 ++++ .../testing/harness/bridge/Validator.java | 557 +++++ testing/src/org/aspectj/testing/run/IRun.java | 62 + .../org/aspectj/testing/run/IRunIterator.java | 62 + .../org/aspectj/testing/run/IRunListener.java | 38 + .../org/aspectj/testing/run/IRunStatus.java | 175 ++ .../aspectj/testing/run/IRunValidator.java | 28 + .../org/aspectj/testing/run/RunIterator.java | 136 ++ .../org/aspectj/testing/run/RunListener.java | 116 + .../org/aspectj/testing/run/RunListeners.java | 89 + .../org/aspectj/testing/run/RunStatus.java | 408 ++++ .../org/aspectj/testing/run/RunValidator.java | 206 ++ .../src/org/aspectj/testing/run/Runner.java | 510 +++++ .../testing/run/WrappedRunIterator.java | 72 + .../testing/util/AccumulatingFileFilter.java | 53 + .../org/aspectj/testing/util/BridgeUtil.java | 439 ++++ .../testing/util/CollectorFileFilter.java | 88 + .../src/org/aspectj/testing/util/Diffs.java | 103 + .../org/aspectj/testing/util/FileUtil.java | 745 +++++++ .../org/aspectj/testing/util/IntRange.java | 118 ++ .../aspectj/testing/util/IntValidator.java | 21 + .../aspectj/testing/util/IteratorWrapper.java | 147 ++ .../org/aspectj/testing/util/LangUtil.java | 1192 +++++++++++ .../org/aspectj/testing/util/LineReader.java | 204 ++ .../src/org/aspectj/testing/util/Node.java | 162 ++ .../aspectj/testing/util/NullPrintStream.java | 95 + .../aspectj/testing/util/ObjectChecker.java | 38 + .../testing/util/ProxyPrintStream.java | 104 + .../org/aspectj/testing/util/RunUtils.java | 368 ++++ .../org/aspectj/testing/util/SFileReader.java | 185 ++ .../testing/util/StandardObjectChecker.java | 127 ++ .../aspectj/testing/util/StreamSniffer.java | 76 + .../aspectj/testing/util/StreamsHandler.java | 217 ++ .../testing/util/StringAccumulator.java | 99 + .../aspectj/testing/util/StringVisitor.java | 28 + .../aspectj/testing/util/TestClassLoader.java | 154 ++ .../org/aspectj/testing/util/TestDiffs.java | 360 ++++ .../aspectj/testing/util/ValidFileFilter.java | 123 ++ .../org/aspectj/testing/util/WebInstall.java | 208 ++ .../aspectj/testing/xml/AjcSpecXmlReader.java | 432 ++++ .../org/aspectj/testing/xml/IXmlWritable.java | 25 + .../org/aspectj/testing/xml/SoftMessage.java | 287 +++ .../testing/xml/SoftSourceLocation.java | 89 + .../org/aspectj/testing/xml/XMLWriter.java | 353 ++++ testing/testdata/harnessList.txt | 1 + testing/testdata/suite.dtd | 6 + testing/testdata/suite.xml | 43 + testing/testsrc/TestingModuleTests.java | 31 + .../harness/bridge/AbstractRunSpecTest.java | 65 + .../testing/harness/bridge/AjcSpecTest.java | 340 +++ .../harness/bridge/CompilerRunSpecTest.java | 163 ++ .../testing/harness/bridge/ParseTestCase.java | 228 ++ .../harness/bridge/TestingBridgeTests.java | 32 + .../aspectj/testing/util/BridgeUtilTest.java | 100 + .../aspectj/testing/util/FileUtilTest.java | 46 + .../testing/util/IteratorWrapperTest.java | 164 ++ .../aspectj/testing/util/LangUtilTest.java | 342 +++ .../aspectj/testing/util/MessageUtilTest.java | 130 ++ .../testing/util/StreamGrabberTest.java | 112 + .../aspectj/testing/util/TestDiffsTest.java | 114 + .../org/aspectj/testing/util/UtilTests.java | 36 + .../testing/xml/AjcSpecXmlReaderTest.java | 236 +++ .../aspectj/testing/xml/TestingXmlTests.java | 31 + .../aspectj/testing/xml/XMLWriterTest.java | 44 + 124 files changed, 20887 insertions(+) create mode 100644 lib/commons/commons-src.zip create mode 100644 lib/commons/commons.jar create mode 100644 testing-client/.classpath create mode 100644 testing-client/.project create mode 100644 testing-client/src/.cvsignore create mode 100644 testing-client/src/org/aspectj/testing/Tester.java create mode 100644 testing-client/testsrc/TestingClientModuleTests.java create mode 100644 testing-util/.classpath create mode 100644 testing-util/.project create mode 100644 testing-util/TestUtilTest.launch create mode 100644 testing-util/src/.cvsignore create mode 100644 testing-util/src/org/aspectj/testing/util/TestUtil.java create mode 100644 testing-util/testdata/testCompareClassFiles/org/aspectj/testing/util/TestCompareClassFile$1.class create mode 100644 testing-util/testdata/testCompareClassFiles/org/aspectj/testing/util/TestCompareClassFile.class create mode 100644 testing-util/testdata/testCompareTextFiles/differentFile/actual/TestUtilTest.java create mode 100644 testing-util/testdata/testCompareTextFiles/differentFile/expected/TestUtilTest.java create mode 100644 testing-util/testdata/testCompareTextFiles/sameFile/actual/TestUtilTest.java create mode 100644 testing-util/testdata/testCompareTextFiles/sameFile/expected/TestUtilTest.java create mode 100644 testing-util/testsrc/TestingUtilModuleTests.java create mode 100644 testing-util/testsrc/org/aspectj/testing/util/TestCompareClassFile.java create mode 100644 testing-util/testsrc/org/aspectj/testing/util/TestUtilTest.java create mode 100644 testing-util/testsrc/org/aspectj/testing/util/UtilTests.java create mode 100644 testing/.classpath create mode 100644 testing/.project create mode 100644 testing/src/.cvsignore create mode 100644 testing/src/org/aspectj/internal/tools/ant/taskdefs/Ajctest.java create mode 100644 testing/src/org/aspectj/internal/tools/ant/taskdefs/MainWrapper.java create mode 100644 testing/src/org/aspectj/testing/gui/resources/gui.properties create mode 100644 testing/src/org/aspectj/testing/gui/resources/images/Back16.gif create mode 100644 testing/src/org/aspectj/testing/gui/resources/images/Delete24.gif create mode 100644 testing/src/org/aspectj/testing/gui/resources/images/Forward16.gif create mode 100644 testing/src/org/aspectj/testing/gui/resources/images/Open16.gif create mode 100644 testing/src/org/aspectj/testing/gui/resources/images/Open24.gif create mode 100644 testing/src/org/aspectj/testing/gui/resources/images/Quit24.gif create mode 100644 testing/src/org/aspectj/testing/gui/resources/images/Reload24.gif create mode 100644 testing/src/org/aspectj/testing/gui/resources/images/Save16.gif create mode 100644 testing/src/org/aspectj/testing/gui/resources/images/Save24.gif create mode 100644 testing/src/org/aspectj/testing/gui/resources/images/SaveAs16.gif create mode 100644 testing/src/org/aspectj/testing/gui/resources/images/SaveAs24.gif create mode 100644 testing/src/org/aspectj/testing/gui/resources/images/Start16.gif create mode 100644 testing/src/org/aspectj/testing/gui/resources/images/Start24.gif create mode 100644 testing/src/org/aspectj/testing/gui/resources/images/Stop16.gif create mode 100644 testing/src/org/aspectj/testing/gui/resources/images/Stop24.gif create mode 100644 testing/src/org/aspectj/testing/gui/resources/images/Test16.gif create mode 100644 testing/src/org/aspectj/testing/gui/resources/images/Testfailed16.gif create mode 100644 testing/src/org/aspectj/testing/gui/resources/images/Testopen16.gif create mode 100644 testing/src/org/aspectj/testing/gui/resources/images/Testpassed16.gif create mode 100644 testing/src/org/aspectj/testing/harness/bridge/AbstractRunSpec.java create mode 100644 testing/src/org/aspectj/testing/harness/bridge/AjcMessageHandler.java create mode 100644 testing/src/org/aspectj/testing/harness/bridge/AjcTest.java create mode 100644 testing/src/org/aspectj/testing/harness/bridge/CompilerRun.java create mode 100644 testing/src/org/aspectj/testing/harness/bridge/DirChanges.java create mode 100644 testing/src/org/aspectj/testing/harness/bridge/FlatSuiteReader.java create mode 100644 testing/src/org/aspectj/testing/harness/bridge/Globals.java create mode 100644 testing/src/org/aspectj/testing/harness/bridge/IAjcRun.java create mode 100644 testing/src/org/aspectj/testing/harness/bridge/IRunSpec.java create mode 100644 testing/src/org/aspectj/testing/harness/bridge/IncCompilerRun.java create mode 100644 testing/src/org/aspectj/testing/harness/bridge/JavaRun.java create mode 100644 testing/src/org/aspectj/testing/harness/bridge/RunSpecIterator.java create mode 100644 testing/src/org/aspectj/testing/harness/bridge/Sandbox.java create mode 100644 testing/src/org/aspectj/testing/harness/bridge/Validator.java create mode 100644 testing/src/org/aspectj/testing/run/IRun.java create mode 100644 testing/src/org/aspectj/testing/run/IRunIterator.java create mode 100644 testing/src/org/aspectj/testing/run/IRunListener.java create mode 100644 testing/src/org/aspectj/testing/run/IRunStatus.java create mode 100644 testing/src/org/aspectj/testing/run/IRunValidator.java create mode 100644 testing/src/org/aspectj/testing/run/RunIterator.java create mode 100644 testing/src/org/aspectj/testing/run/RunListener.java create mode 100644 testing/src/org/aspectj/testing/run/RunListeners.java create mode 100644 testing/src/org/aspectj/testing/run/RunStatus.java create mode 100644 testing/src/org/aspectj/testing/run/RunValidator.java create mode 100644 testing/src/org/aspectj/testing/run/Runner.java create mode 100644 testing/src/org/aspectj/testing/run/WrappedRunIterator.java create mode 100644 testing/src/org/aspectj/testing/util/AccumulatingFileFilter.java create mode 100644 testing/src/org/aspectj/testing/util/BridgeUtil.java create mode 100644 testing/src/org/aspectj/testing/util/CollectorFileFilter.java create mode 100644 testing/src/org/aspectj/testing/util/Diffs.java create mode 100644 testing/src/org/aspectj/testing/util/FileUtil.java create mode 100644 testing/src/org/aspectj/testing/util/IntRange.java create mode 100644 testing/src/org/aspectj/testing/util/IntValidator.java create mode 100644 testing/src/org/aspectj/testing/util/IteratorWrapper.java create mode 100644 testing/src/org/aspectj/testing/util/LangUtil.java create mode 100644 testing/src/org/aspectj/testing/util/LineReader.java create mode 100644 testing/src/org/aspectj/testing/util/Node.java create mode 100644 testing/src/org/aspectj/testing/util/NullPrintStream.java create mode 100644 testing/src/org/aspectj/testing/util/ObjectChecker.java create mode 100644 testing/src/org/aspectj/testing/util/ProxyPrintStream.java create mode 100644 testing/src/org/aspectj/testing/util/RunUtils.java create mode 100644 testing/src/org/aspectj/testing/util/SFileReader.java create mode 100644 testing/src/org/aspectj/testing/util/StandardObjectChecker.java create mode 100644 testing/src/org/aspectj/testing/util/StreamSniffer.java create mode 100644 testing/src/org/aspectj/testing/util/StreamsHandler.java create mode 100644 testing/src/org/aspectj/testing/util/StringAccumulator.java create mode 100644 testing/src/org/aspectj/testing/util/StringVisitor.java create mode 100644 testing/src/org/aspectj/testing/util/TestClassLoader.java create mode 100644 testing/src/org/aspectj/testing/util/TestDiffs.java create mode 100644 testing/src/org/aspectj/testing/util/ValidFileFilter.java create mode 100644 testing/src/org/aspectj/testing/util/WebInstall.java create mode 100644 testing/src/org/aspectj/testing/xml/AjcSpecXmlReader.java create mode 100644 testing/src/org/aspectj/testing/xml/IXmlWritable.java create mode 100644 testing/src/org/aspectj/testing/xml/SoftMessage.java create mode 100644 testing/src/org/aspectj/testing/xml/SoftSourceLocation.java create mode 100644 testing/src/org/aspectj/testing/xml/XMLWriter.java create mode 100644 testing/testdata/harnessList.txt create mode 100644 testing/testdata/suite.dtd create mode 100644 testing/testdata/suite.xml create mode 100644 testing/testsrc/TestingModuleTests.java create mode 100644 testing/testsrc/org/aspectj/testing/harness/bridge/AbstractRunSpecTest.java create mode 100644 testing/testsrc/org/aspectj/testing/harness/bridge/AjcSpecTest.java create mode 100644 testing/testsrc/org/aspectj/testing/harness/bridge/CompilerRunSpecTest.java create mode 100644 testing/testsrc/org/aspectj/testing/harness/bridge/ParseTestCase.java create mode 100644 testing/testsrc/org/aspectj/testing/harness/bridge/TestingBridgeTests.java create mode 100644 testing/testsrc/org/aspectj/testing/util/BridgeUtilTest.java create mode 100644 testing/testsrc/org/aspectj/testing/util/FileUtilTest.java create mode 100644 testing/testsrc/org/aspectj/testing/util/IteratorWrapperTest.java create mode 100644 testing/testsrc/org/aspectj/testing/util/LangUtilTest.java create mode 100644 testing/testsrc/org/aspectj/testing/util/MessageUtilTest.java create mode 100644 testing/testsrc/org/aspectj/testing/util/StreamGrabberTest.java create mode 100644 testing/testsrc/org/aspectj/testing/util/TestDiffsTest.java create mode 100644 testing/testsrc/org/aspectj/testing/util/UtilTests.java create mode 100644 testing/testsrc/org/aspectj/testing/xml/AjcSpecXmlReaderTest.java create mode 100644 testing/testsrc/org/aspectj/testing/xml/TestingXmlTests.java create mode 100644 testing/testsrc/org/aspectj/testing/xml/XMLWriterTest.java diff --git a/lib/commons/commons-src.zip b/lib/commons/commons-src.zip new file mode 100644 index 0000000000000000000000000000000000000000..9ac8d718012451cfe1216a1a3a90b313d27bcd07 GIT binary patch literal 300608 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%0A9%BNsiwI`s>Oc}0Iy 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�*x`p7&W5@AkkC$^2JKMHQ zU=4CM{(g~HjZ2N*H4E#6)HU5f?h;oj$O`qodU%AXAjXUbki4B&$;qi*S!kZM7M<)Eew>u=Nsy(Ot)@`T0hTB#bxB|Gc zvvUr3cypd3ToAk_74rzF0w;WZjj;Vr6 zSG5Rqs6@UPn9-beF#7v?cO~wAoDhzPRAlduH|%xtz*-bN)@?~XZwYb3j`$0{cIugo z9pdAeH##n{DXG0K9st@lvGK$2@de$TvMVfO-~8vno`*=!Hw1No%tz1QX^&HPvvjb+ zHrNuqhn`S6Hp-WOba)X6j4tI}++u`bZ_Vd(P9P88%W-sI#0#rSH@nv>`w_i){?_;` z{f`s<$dj%%ihCx$y^iFYMM`%?lIUrus?(&8xCz`is7_zJ?RWMIM(x>jZUdRs-!Dsq zOr=i}gLg(3P@nyP-3#L}wYDz{U%Isp4!@t4JJqydU2(n|+JoBU@$nmNRRrVvd{wn` zw|sl|SJ;Z^oolKm`t|kWcC}4EBg|ISWLx#I^^Xp8_sX}&+x#EM{{oMoh~3b!qw(I+ zBLV>l(*psq{ZFwNB~xct8y96$m;VPv?eUW3zcEN!w5;Pd{-AvM1%5;76$xF9BqleD z&27orb^YO}GFsVwPy9!}NlCNJj-~5JX^ulZ?A^f@AlID6@NGjBlt2h)9b`2JY<#>& z|0L9nrLV*=Wb%=A$F4T&`Q$(OmEDw2o*ih|w31AD!2%XYP9&EnOyG)Mnkf%E#NFAp z`=W|8QPa>R1x(*=6Xu0Qn&|q(w!H7zJOFw8p7+%+hacMuH=_$T(>=aU&r>gCX(@HB zjxqx;y!R!`)&tj`BVt~qYMF%LZ06>%B#U6|XK#YHP6KARfcNtP6B@O*JSl*k$K-sB z>zFl#;xC2U3T-B&&fvofS-Ew`aK5j-a6*Ejj@Y1D(L@GI1QHODcS(`O0hzHxI^+91 zdQeKwfi%7ea(%S6pjj8ms2JqDBpEaov-5Ot`3k;-)P#*{>1VzjB;ut`DkpL?+BBbG z;}+ZW0vuk7R=%0Fq|4&A6Y19kdkcggV4*&{MgwyBsd4}qe!3tNQ&qQ(S za+9&E!A6p2HIKuwDKO2qG){l58l0SFRNHDJTiJ5BLuy0-vg9A6jid##2RnuDU}4%q z9|HeHdk}P8I$eRVfP@x)4;)G{O{|=Dx-#k4@$s^=!m2AHwWT<*1NVwmN}DN#?@V2Y zx@9=qBHJ@H^IbpGb7{93lm<|CjPU!&Ti183glCEwck}LTzg|SPZ7?YQ+yYbZN7Q;P zm{Mtk5n)yG{%S}OcV568`Z=E*b)Gq$rfUsVk5b-dFsqQZWFrY6*so;}D2gZ4qXqB{Bybj;RkNj)@oZe5(QyQ)jJ2lnQijevtU>s`VSq@AaH^o)CVox0USk z%??GbjEN&J;q>{M0w!Dvsyg>kyooBuO_B{~b25DHR5e{^+T4UKFERU+Bdfo1tqt}g z)XmZ|Gl`{#q;=240bcNlu_)W(S?$UPeF{)d+a4tQaab#uDHi-D>o0PBoZNZGMpn?K zB^Xm0obBg=i$J_4O1hdAQa3mkBUAUm$!T@+t?LhDf8C{i@Mf`^FLr>8CkY*Crp$sB zZc<~JXw%fW5<~q7&Gw;S36C(xWi?_IbF{$ zD(e3ytS5DKso(Rs$50i@rXqA5%f3MJ%P$y9J$koDqSkO=qLp1RqHxF=t9CJCfzjU* zts9sl8k%F4@NuWKdBn1`@2Itw> zmjaFH+0VhIxpeK!x+0>m0R!5|C&p9o5Gz5baf!re?DZt(9OkvhRQxjRe@lqvCB71| zV9h(jbkfIs;>@3F?=Er-#_gQ&l?WCmpYLyWiQ$1>Z@kWA{=OjlDqzOD*vk>K0pUx1 zwg)k}!{#B|G)2F~4lkDAX~|Hm*!j}(d^vnA)(lST2?N5=E(Gy9fDVSq|AZfj)lbOS z50+_`>n-UXM|^7f-E3s99tD1^h|ctAk;NXBiN{4#$I3w_?=l>BJc=IyBf8s&Tg(OR z|96_fs}u8>E~%SIa!`_-g>*&|3GAdKQK}o1HH~>vaxD)}{g-W~>!p(QFOp$`lKp{z zY{Ct@b-zJWz&aQ8>a5Ms_Q{o8ctU)fxF+pyqJ)J%JG@d(36;f@vPXKu_XWwy%2WKqpg9a9R6nhVuE)DG)r zLiJltx2YOAmd~(x(lEpSBG=w zwH>b2N(RQ1#zPJDhO(1c*%hZV1fC^^DA>jnJ6;CII95?Gyei47Oqy6U&-W7`k` z*N?f-C^-ZoE9)8HSJGi|C2PZBM^6EkvoA9Oy&iAepjxrL8IV@iwT2ozoA7>)d9x)l zZvK4_8{_K_(|nU>jthqlX7&v{FOz7#xoDpO*n7t{rZ%JisHhMJ1(CZ8eM*-pwv33N z58^e?b0>F~MO?0zG(1qf*KgDHb%RHe2hYds2BxDSSRI-!J05LX!Eh$F$HSkMrOBtrW|E^?r$O|0{ zL}^ZdMZJJ;Ez2uThN_Q1=1f#eXvIcf%anwuV4@`R5JHgMv^u>Rs5sRm0AHL1dejz< zK;mqq_;>nCX_@+spunoP3ZK0?9T31Z&LWD%E>aQj8BfUWKX4$lDDZv3$O7~@O`ftU zVn$vVm9ZyX7?BO8Uh^-R)K$qt&WrJYu1*iuq<2(B8f1E1k#%s~v#BWAIwCfjZKmo3 z^a=zN>|=l8ep{7!RBfU{HkP{^U^I`cdAV$1Y9fnG35={MuldhwaXwfAKMsctey~PN zt{mF1jwO+pbM6$)ViZ@_SMILS~`d%DYMc z@(~pD>_W?nnt6Os_IW{lrc2;0-ebOiY?D?k>t?2M=rmB?Uoa*T9O6DpN)8j67kLCr z5=$fjkY+GRudzbfJRMcpBwHfs-Q}_u)Ztw`nz|tX`(|7E*$`&zT!I zl>r?Ec+6~%y&w{KmdTU^21$s3DaWgrb8xPdd(RHg8aUAwn{J9)oiC(_I-!lOR5}_R zEQW`sK(;n%&6---MPqq|Qd^z?*5|Am$CzK_p;Dd6WjMa_I0#WR(a(JOjLgbbk9N+j z5~{0SHKacp^0c!L){-@SZcic%UsaEp4j)o{wE)Wz*#o+q&25FT?l2&+1s7%#|7Aky zu#nwwyo$cKlQ*x+EoEhgApkSeV^s5cES&obuSMsXyK9|PCP=h*_5P>#;9#`|0 zQ4Ras%IYk<(Al7*RaL|(u6+{fk^DWWhHLo*CW%%OhN$;aaFXm@I7@4md{OeFuh=7} z-UU8g$@*l2p#ywwaz?5qvFh|>qka=Bv(gKn7@LO6XDAfzFG#o@&LR?Aq#xK(jyUrq z-@o>p47O=wY3CVzaQpz3CK@#z5bgzKk)~d?+K;sqXg_Krd#D+nC>bwN~+tn5QP zSS=N0t@{29w~{z}?*LS>m?nO2j(7uWzdv$VvF7N6dyzU^?VSGbKJkG(*b;uw1QVFS>(;nftr z-v>O&S+w9_z&ez3ZR9l9(S*jdBB^u;e2tF3P843%HjuzFdIGL=Xg21-ZG;b;fD1MP z5wI@ZeXiS%xO*XK+*X;Fn7S~1JmUzNwo*7>_SMiT^-YCsB(q;3qs{&19WOUuzpgy5lbSx+Io=j0`o*}QQMiu9d=J;Bl~ znn1rCHOVZhAOD!Kcq3Mo`8U==@aa^s#@(c1BB*&$uAD9$G!e~gU93_> zX=V$;qI+CtlF(MC&#-^N*`pI^YWHi7G{S}NDt>SRcwMjfvcbf`Ngfq;RqB~4m_JpQT}ABEp&ex6T0g%5tlz*!XDi9XU!tKd?K=e%gt&XE@Rg=JwWHEQ zKbYTDH;gWlrUi2r5bt|eKmYY6ANRSWtC0fVT7d-u@@EDDV*USqlS{goIvKjyJ26-p zx*7g2EU1pwf5(DeX|~_YxzHxnssmXK)dBlOt-*Xp?vE zoNCi3y^kIkjusVjnzD^62}*cH-tJyUJ-2B*TF7*hD_mALY9@hK3@Xu@29@f#B$hUj z$alz(-W~k?+x_?Fl%Jd8?_Wy6uZP1|=sU0{%)%4o2VI#SWt%mdBT9jIIWpDny@1lo zVq_79gy_7T{wg5n^cXB9x&2$uj?$pS!kY#vLwu(xFsBGO3@~2;`;cCZ(&8)pL`wO5 z0pE~I4v^djx~w@gqK*(OVIeX@5=SuU42=fOR-r#@lk!S?@~p)~Y*~qPhC}fZ1FU<@ zZ)1z`<*NzYn1=Jj)`Gey42S-CB`6e!tp3;DP8$fq97Yr(g0gEdLqT<)*gD11Sp$R+ zLc)D3e$7M#@n!-j)ZYnACTEiU+sSsZgCV!=W>W{O8hPZ@qp3;;*bQeXb+>A@OpzdU z5oHCt2y}O-C_qH}DV@^Ht(w8Q-2v~!s3m{m+}8qaY0u{_116i`G`c9`4AkL+rOh@c zL@-KJHjqdn9ok4GpnB+iaL7uqi@$bD0p;V$QSZv?UYOBl@qNUiwuplFLDeu2Eb5Lt zL;2P|IJP`ejNGcSbm!f%uRI2@VXu@~aC-EZu0<}I3X}%ln@i?4!yp8pxC^qXBtBP& zu=9GQmOk?rH7ggjQk=SzeBxVa5KJJmT40s19PNRBGQnExpkj>yb7)F=LDYh>8ca79 zRQ~@#*gFPk7DdUTW!tuG+xAzsZChRJvTav&**3du+qUcW+<5m*%*4BI_RsTUpNJE2 zVr8zCnTvtG9l`{tHJB_T9Ohy8QW@^eQB_o-k5No2MCc$5<1MQ}N=$TC7?$y;Munyw z<&*Qcqz1w239ub1yV(p_2sW=&laSSd$WD;M4in(Qm#lIDwgTGxM=kG@t!T;eUH3#` z=9FC8YSq*%i4@_4_DwR8(jiDvkP^j?OCIiBVpO-U-O5dR$V|x#ES$Y4e9^L{TdRY7qmXUcJNccZ%-P7L5TjZL0Cn=pFPCKj2bOxT#gR5s2BIvmKmVXAWbdI&h!gs zNH&Xv3J}YufbeX2T^5{9Q%gN<2Br1`gi8zSd6*0_?7Sq|g=>R8$&n29rcF-g6PHQ+ z{y&F!9PDE$MxE*bENr&U?De%HvC@G~(t>r_-?McLe}U0(ZNPL;AJ*~OhuWFKMB5vk z%pUg)h;QCJS#YrauoDDHp$s|W&lAS!Zh%~mwuny4?e1{lW<~)k4fZws?TJoI)$mio zuH#oLIM&VZgWV1sk&9od+i_Jg3%p1dR5T>sBm+H|_cY@v?yh2nR$0QqDiN&zE1L|l zcqliGx8h9sM_NB-6cn-_f1#)4z0^e}%w%@?RX11;uMw2DJ6Y z9>Y8pjuCe|^!5|BJ*6@*DKb8GbU9sP#5`1JpNBJJQupV!XMGa5M!UbezT`eU%=(_x zV|i6=NsRru?#sH4X#+){hBIZaD%@4PDQH+|eEo$!VlDrth}$ddYcqOFFZbyX-u9ja z*$&f3{NRLkL(-d`EkRa5bWzX%!v8T9Z6ARH`3jOv+P=?}qFiqOmKtMr9b;D(2OCAu zbzVuJ96~Icb@ndC*5=`wX9NN_nu{E|i37Mv+_wf6?*%4!Eop6L1G<{C-fH%{J0!r($=a!(lC&7VKyL0?M;{UNPS|QD5}5C+elIGa<#0x zQ+0eUp&`ybXfC9aymMyqSDE+P-?EJ)O+!+g{yGtl^f?(> zij(9_XyV5%IVeX_%TSZtu?#3%2v9QmT~k{lTL!<=3gIkSm=KI-FI@FVGe`VVUG0~1 znZ5Mps<3GQLX163J5UnPNqj2lUuY^v1VB*EHTtz5LjPeP?8@eYRi>h1xEGhHy&=^^ zu%Vz}tJ-rSg*U)V1~YL3gkj$_7g`#@ThYFZ8v?pb*oH8NJ1SX>!7NxYQ=HJza;Y_h zKY1KTE0lpBpa&$tl5Z*=@*CZ%IG?I-yCFvVq1F!@SQ3t48!c+hu9vswceVmg9Jy>q#s*FZ-?^#A%dLzgm zx(kvze_YKW@F2%F^GFa24e=p_${thXsz}zFGYzyF-$80>& zeByvjRj1c#B(Pm&K#}z*xc>|2vC~23(|D6mQIFBo_Rr&k*=-{0taycgJY*M2>9B!A zbYbx2Wz@r1>u_(Yo!?pz;2aSyb@g#Z;a_2EC%wzkL+8>?nDCf4q8OAP!IE@D8 zy!s*3Z?%_JIK>#4Iv7z`@h_n8dX{6in@u})V{St_ngxl@Qf|G>R*OlU2<&}gtQd}o z7Ryx7yCgvt+Oe1CJ3?g3qamrPJ$0VE-^8XbgN63d`_b$^wAVAv<^nc!j`FwA^+4TQ znD*<$x#v*U!_6Gc_t`Z#>r&f?Kz;;Aan}fBqRn}dq;C2Eo*>E!RX3q4FW)f*}Tfzu(hDV_IeCn_TI$Cydvzj4@o<1syA70$?nV*@X z+1&^77&Dd=G^h%cn8({E*@x37*<1PFpP~w)nw-Bm0cjt!n7+U0cURc5w1lNR_TtZUBRX=QrvsrcE-*+-?rbs^I=< z%!0yx29-?7ol9}f4prla0x0VpBLEBIX&P1Zk&VW_ z1a}C&Baa*_;adl3nN!G7^GSQP;pWuEZ9tL76YU-D~A$e;`1c5xzmyH=e1)h;^xGqpJ9+ucG3A* z%t^4hrM5E0)TzEOm}l9gx6!}_OlY(Of2D{DsYgY%1^C(w&+3tH%Jq_%i1$m8=#f-c z|B;5%6cr?FtA$9-eHD8qad3eIss29t)d5r#5aD7~XANunCM}HW+*w#KQ54V zFwo|Ds5qm)3u%)DB&c#J=(?=Xi(g~z4`H%_=VcO2fpLCMo*BVOwf#|6AYwQS4l@$& zeH=4rk|gN>!ezU?zrm3kmx?mbis#TSo?D85P8(}hZWJL_v-%zO3%22}Lcn)z`t1P%nf4=NL*VClwkb1uBwArOQKmgXubPgb^t! zfhva$#p&4fJQqF>D3RB1Repo-cb5*mo&do0wM=Q2Dw`Lnbw+(;Z6C34ekd%p0Q%Gv zWNih~NTOv6zF#;yTNOT9 zFo33JU35Dyib{_Fm~s%tFK(4yq@5Rin04v!9opBU7@2UV-p_)gb;uafGaXISc5!U2 z^raDMH4qACv3egU&A_Z)2dXn3pY;w&4|{bd#7;Brwb_5Jy2-zYHEldgBYyLDf@k~6 zLIauhW!8cJQ5?-Jx;%&T-EbQvZ5jY>$I=g(6H>SN9SiZ#(F2!!Za7%*fh_(XBfI9Y zr(f_ddTi|t*}o|6`hxX(8|0Imgz;BuPvsz_@;A@y)I!llmE?=C?ds}A-cBpZ8L z0&i9sNx-|IQO+l2pjwk>B3&e^w^NFY0S;kGciDW^&Ig_c*W%Y<&#ji#m0F0e|0shB z{h&%Hc0P2nl~_uHFJr#&@Ogb8O^KA_|3ew#$51)S_T=gF{t11dtYH&E=>AcL*(V7x z4?K>DG$y}yQ;%QX4ot-l2Z*nrvU#U)PTuO5lRC#ORvA0zHSc8)y13_L{x@`iR$6ny zgQ&LU+n8HV(vp5ldK)n7_Ef=S(goE@+`r}<)jFNc?gThkBWFKgN#eg0K+3IleJ?)@cv&dZoce_)9hF ziNp2r9e0a3l^q}~oQN>^_3G#B;~29wDYUaO)5A>^TacOkdeR+<_t@=l9mFxGe4HcU zX4`!$;W50B@JEYWS)qJHv!_9kl?w_LrYe~)oLB1A0yzlWzPFGRaJL1aJTPv(BC5Y& z{d3cJiQq_D)=}w+nxVMlt7?o;8i7hdmsl;e#UInhtT1KGCm~N`PX}yQ1%ea6gnuPq zrxo+Qle8Ky`TyMm%OZ367Q(GP#Uz$Xwt-dqW> z)P=rnv#n2SR^H&@bB zaz;#KR}6xNS0aTF*g>}kCp(@PhCMCmta$HhY}0+>@R11N17PVh@Y-V0C9=qu0?Rw6 zj5mJ*@~^$2pQ)Pxjz*v7nU$Ndv>eaCL9lD=L&wJpM~aLA3X|wBXM=+2=8dKl3AHp1 z*&sl3#KOA+bLD@BmC~I&b!8Zq5nke&hz80+5QYPg{VqQW+F!7>-L+Simm{aXkTmgl zSLeHFU`y)ww&@MtR+2zZO|xGMlPTHMSKGYVUD723r)BCMks9xlu+D?P*BD( zzl@1!<-N)|lr0T_5Th2VPxZMSMq`K9c+wvTEJ!50*G1<2^g!|*v#wle)qFtUV0NB_ zsnAk*e&CardvBgeHk`AJOOdR#PDM>dKuE{bYTyCVIZ=Z3AO|ilJKRQYmL8aP3Nqf1 zw7)2M9KOepkA__D0JK;@V139jhY17FCdqficKFjz$x*p{XBxfY*H;8y>N__h(yK0aAop>=-B)aV~3{v1tQu`lf(F;H|$Ez$3uiWZxh7S&_@# z8I_u#$- z;f7bE#t)u06hdCLB?2INY^`y5`;V7d@?fN)8xUMh&moFhASsQ+D5OB^mX*|GAEQ8*j$(3iH#N z7kG`?8|zB#dP*@eJCF4Ue0r9%cIQWepD18OKc-Ilz9|X28nlr7RK(%oS(2Lc_|=dI zKa7+?XYu8Yhx=f+SnqxI4(gD09}aWXSxJ1lRGqtmnAu~z$}?A5(iFsD@^9laUrC@2 zD8uI)<9~(E=RuV*r*J?(<1|43b4=9#fzSVKq3D0d&&}FCaT^>cKfOY|p!HNryiSLF zfBM`A+db{<)Wh|BW*U+p#DnHp>|97?1ymXi2;R3G!jh@Zo3n~5ILFV05iI;42+DpA zKW@fQ?+4k7WGNJ+15h2b1Xm}&`M;#ZMe@M!+;JrvdJWlRPzfe_(15$P7X0a!{W^;J zA>0?$yD?okc_du1RH*TFcQ%qRr)lr+-yYJwP8i=A_wJ9*s`3;9-YFB=b0U$KlE;YmTsN$w5+&iTO!xCC`l4Fq10bfDpV zyReN$SU0i3wVcprV0!oaCwT$>VV4{z9Z?IG(x^mxNGwpIg&Z>Cl1$@6x%Bv=UK6Rh z6v2!to5DcMi82zNVy4+qffban1jbfY1DPEoK9e6$EJ?sEC}=T(I}Z0NZAoXw-WL6t1Y{1$-i)tzjw_P`cuo0o>Gdc zqs1pJ43P?*A>x9Wnj?dzIC2Q~sWVGr=@>bcfY_R6;Cr7V)jIv4zZ9J#o7=DDz3JEo--43R*rk%ce`Siw^= zz%=7Ruibwa(j@bRQRhlw{PE_d?or9)3uFn@5jMpkGSsEZyCOm(piL%U{k1yJGLBdO|4x$lEPM#=BYvPg`|aJQHQ1H-Kfjzy`>YWUlW)OgXG zjtDLg8~A;L%cvcLERcaKG(LL~X4@tL4n)f^kRER^w2uktERd)qN>*d|AzR_DL(-kN z2t^NK3LdT2?Xgq|5Hzlc0ApZW9Z9ODL17HE$`Udqy%!@=Gm)3pJSh(0Q)KCeJe!h4 z!qJQF)Ipn`tLGzdN2$^H5>g?%`)<$UbrQ3Q{Py({gox<>nE*FJ`uY*n*1IXMw-fG> zM15y}W6$SN9MQLZX2|!T+bh|1cWW+iKjPqX=qx~bF#dbfvvnKJ{VxSS*UaBm?G3b) zarAb#vOyb9wzfz6t~E%v`xH*x!HPo|1pN5Ue%E986V4Fo_AJF4iXPcOr{;jex52C+ zkBeBMHBS?+%(k1&yWg)XAz8R?_q+B?p#~D$RpY*VI8$BYSYj$-T^WjDNq4sAz%NM^ ziVS+Y0ghe=)(}8p37l@>tjSGnI3T8d&oS=oKhyZ$j-V0k|I(7)3&P`p^>)5y@Gu5s zS#UNHwmSsQ7LirOY!`*72j3RE#jSuS0{qWry4HM;6I+wU$0YZAc>PY`^?rVD$^q4k zr;xC`Y!PI$;Zi{a!sSE%`)1D9J}?>%d@f}RZ}^3?9u%GR1>%b<>6&Cy~>s{eQ%@wtc%XXwcxDtXDh zaTl)1jf3U$d;9>-=FK}w4PFOBcq%a);4g96*GuR>%_*}5FhZ!_bx&2ZIDWp}@~DP9 zc%Z4btcZTb$0wqA$`IPX7S)um9P3!MvdghxP zE5Lx0{kYE9O| zmW>0fGln==pm!|$rj6=c-pdkystOpPK1orBJ)h7~?dcd83og3sn7>amP~C_HaMH9_ zz{@(;Aa15e9fm0tl|j-5kV3?1*tz4Ed0xueQtIxUyO40NEG99cRG?gvimHewLowO# z;Exp@lkDTi;o3?-K01&UI2oVREQT^Ti+?T}EfhI2uiC1pC7`>(1UUI}YCu^UH0-y& zX*u|?ml(QfP6aUZQsF>52*ECDiIL~>bctytWaVhdrgb|-8n*RCzZ-}W zE{=BZ_j3*RSwh^UB`0y_EW<{Pj3iy0oO%7K)h*spPTepPO{x-HSpD@3>`Z;=WJ=VR zDX);0T&r^b1}r*5Ls~|&K*S7+tr4sdQ&MsxoqE+bArEQ}M>{&yhSvZ&_clg9;?4%eB8^Iw>9 z?21~2bI?VXa%98E`kq7FLC$TG)l$|iF+nHQOm7~2rsS_WDS6t4nGIy<+IN;JZlYxP zRPqC!_+?%y)neQ1vLkhks^j6{x0Uz7AQo4evmQP?9k|A577Ptcv#IydX%}os(qo#71`nzr|AaejP2lmebM@TD#0PC$wfe{eoyvo$iX&+R8S% z$jtv0r$eB}7;_KcnkL~RzC?XSwFt}3tjFhywPRgoE5xd+e5==PppwPHbunCeErE#A zaxcZm3X7dy`;pI(TW#=%`9Q17na{%vvy~qV(yW5cwb2h$Qr1QAkXjtusV-D0hB&?Y zP$(571I*#$Eh|&%o(BHWN4-e074ub6SRZlp^7}r^{#_-41+CDbPW^1L70<)*u;pwh z-l&2nXs8{8CKVcN4w)_4%n&?>V=*Y(BK16Rf{Z2mLXI z3oo=5jZ+f6LZsNYAdX(%P0H--TAt;qGu!4)v4a}o3EvltU9PR|GO*uz@OPIa61LrE z(@g}OS2~cPYkX(}rcgpO)C}~V#lRGXLj^6Gq?0k58l%{ui|?C ztNAvYoV9OIQcjM+2(*c21#ikT0qG!7AOZo?K6dT;hPci_YajM;dh$f{ky6Yt^=1u09hg1FZ!!UUDUgi?1QFd=M16z^|gN zSQ1+1r6dI~7!phgi{kh5YOXLp^3&v(SCkY!tJiQiTh^_ILCbxxO=k|RE!l$agz>Z> z@7=eI1Q}h_T|c-f2@F@0VbUL2SoyUUi!@yZ7c{Z}(?!sq(*vBwX=tdGCbztWmh#;z zhUIHL0b4H?ZyX*+BTq3DZ%bc~IaY^+kD4=6YAG8GG6(uSLm;jtqmUHje2ZS0qA8FY zpc@U)LAsnyp9#gC&T@JJd<1k*Y(s$lWh<#CwT3_4zJqwZV@^0Gj?v$9ZO-d8EdV+t za{%S{#lueKN11P5!|;Ud+9@vkw!_joV`5*t19D$i?O%86=Zt?a(wQuxU^tQY6Ol81 zKt{gi=}51B&AO36PTV0g3Qva8rZM-piyMVRq0HtWf@2<-l@yPPUogbrq_}~QK)_FX z2H~hH^4s<@{73z_u=n4D32hxWFPq&7TzX_dKtb>6Ed+&aNUrc%4Wd*a~i^`CQ& z|Ng>ejSc$)4kSMV!yh9gy1H=@8_Kn3qa;`4;Z#)g*drO|c|ThEIRsp3G%9NS*42O; zVz)bTXqZP5B4&C7#F=btgwZcVhn-o-_cK9Hws>GzLasIL=v(A5@pj{=1x+!&eysb>lasc_UXb$~?h2gy%4LzN9~()LrzQ>8i;+`+~eSwf&CJPT&`HxZjr}N0dm8B zd|^Il#tA&?2bJ`4eZVqFQvxv2nApsGNy>-98l|G{Os=V7J7BmUK8|6ukrz{y=S|V+ zi4hf#thBQZGfP?VDOtqn&2Hl&eGMJ#|R4C;R7aX5GvaL8L~$#;mnN z=rv0%?hRGZ6anYqsYUYL>q~QXPgLvJ;_KVG`JCBgImvoW@EpGh$Q@*fMJla23+O5_ zwA#klYdQnr*IgMY`9v@!Y}GFmSk!U!VBnUhD4O|TX!L-o44M_6e~AtvC3Mo-pYme< z<)n%AXgGMMdo!2FNWV*EzNd@s`wByX5|WR5Zb*2nWNdt`lEn1J;pAtlr`WS93iMpOVBl>f7RU~hnNA^Be-U~ z-Gzb}ob%b!*1e_d8Hg~am(+m+d%fU7em4==X)q9Co9t1q?&UM^58iES?TCUr5DT2% z4tD7J!e2|EaUmaF!e+yBI9=Z$V+7ykuiDg9xVE5&Nw~1|;sQS#172W!0gb6Z+p4g9 zIP)Hts_r1U{!1FDEk_I<*DJ3K~7OFJ^$XlK-NbtH>83=43N~7k+y5V8DKLFL)%ycX**MNiF?pkuri8bv+ zAQ*%J`bM0Qa9;nmBUbtBi+Z0cIf&TYC${-+TrNqdDqKs@*6sK0k)Fo7zi9aKOx58! z)V3MvNxURJo+p{QH8BY^K^w&YQUxa5O*W&`FT$9m=L*R$Do4tU)IDhm^h)SIR(b%A z6^{u=!o6oHCs}N$AXuyy(~?>_yhH8)!*SyqzTQonv%=Jg(y=JHR?sIu(#Bq`O+Z)h zN)@AiPeG3>d-2cn-wn=mPg7aFlC&CtTH233U20b!zWbl6?H*(+27nb@Z;5oR;f}Yv zR#jj0Na;_(@-4}a@r+Z_#RM;>8JCfGK__mOLk+KV5rL`H5fAU_+L52$?k?$ieKl_<$^%Cs1FcO17HR-5ksXU^Z0TQ!+eB^&K zEbu6_;%OoG7DB5uz>8a7uIu1IzPNGDjiLc^FBE7l@{6JCq%Yg})4O87eO$e^3Qob! z)b$JXOwLlrt8DTZ=?Rl{(GPv2J+~iEZT7b~S#t{7`-Ic8)M+hbQNL*dP<(ujK65`!+O3&X(Xn91B}f*s?u|#$;qADha51fLRg>36X=$E zP2C$R%|aJXf44P6q|H^0FXb@%E(x;oV4pYnX;o2D8{>y7lvzl+E4JbHJ7#+@MAM($ z-eLaNvbwwwC`<9LXkQQl0df8hW%WN)0{F!@h$JGR;3Kr{0i`ug8D0P7dv>3SDx=d$I~);Dr!H z46$bjt+MJcd|tb;8gM-P*{klM%T={y@S_*Ws$U_42F zjYeGEFg~KTo70Oh+Y@E4v;utCRMZz-(4y{YzIZEVa8@Ryv1C%dg=2!Wfoq`cXsSsGb_1p(DOiR zRHlU1^G*{yigj>AF-TwkdISmJLHU%xsqrF9AUm`XGGO-ufh52wk)abp#W2B0 z4GcLNGqC(i7X(AZF5q_9#kdsuN2huzl`u&wIN&>u)qhE4GzbdAR~t|=6q<2S+bcb{ zr|jbo>srHF2tn+QnkxTdLeK0^Se?u__2*{u-ndNyb>(R%*|=E3?bY_W;u7+CfxLT0 z1PBJ+?Npepa`a?r8tThSmNF;HC@s&A##*iNGmVDK*;>XjdOLEs81gw8n(AjD$Z#hS zth2(*Wu`B(aEH?l;^^*^eof(JOha0|ZMu$FqEFA2BdBgUPxadt>oDbLEkxP@@h}{P zm-1ts9j7o4nVZDUv0<%@tO9?i5XKan`Bfs`=CN!)jN9@p;=8bn#22G1)EhBj^QJ~_ zE7IXk!#x@Z;$lw#hZxgle;Z4QHNxh=_<29CGqn%;xA-yL+Hv;h-M{kgt^%vzG1?wBgyLqzgVztB)4UnjMi1JB`a|SPLBJ4!TT*)BwNY_PW;sji^Wo? zMmz*DB5_#^c}as1XoYf)%vOkBjo9t0pjWk~gBx|WW*%H^!^*%M@id&>fVT2>SRVl{ zU23uls*vklnY*5%P8V4dLtL_p8Q{-wVVV&p)Xz$+vSwBp=5WzI+FfnzH_L^%%EPKY z?-FBgZS;oQj{8r>ou)z-?q9obJd7} z-poD|ZHqL=mH!SOM}W*gn`-6RxV5$>k07M3}5#GdrKIs+B5(`od<(OWM>2nVR zT6HJh#3x8(!erOQT0;dd3!Xp6g^-u)BiWkr>$Pc$lDbSngGTtTgiH8x=)~*$p4n(YiMY+Ror#{mcji&hPmoYTiLs5|A%3@1Bo_xlJOtWlaQzf%b>eI0tRg1H*@z z4-Jkdlq)aPkRIFt>{QJ&V(sr`&vfo~)irIf=hK9n(ic-HqY4!jwf4^1n?;q5(KobbzBjIikn)SIt`W&H@~+;jf*zx1}k97;r^SIgPn zD3cBl!6h--eAe-5T*XE^t2>r&On~;zMDk;TE~W|s)Iw=V_CwN$IKce8(+TmNyNsRZ zu589N0t_To_H9ky+pI}FA19k;SbC^$WOx?4-%F;&B)RlTEcVj8A@Ek=sufi+xUm$9 zgc#a=CuCxuQYTJT$+P`Qocr_7TRZ4X=Dd&gqSO~0j|Y=dp3b6$hzN8^ReBt)xQEYC z#Yo(~y6Dt$=kWX97I|I?(yQeg8w0cj_%4kHuK9;qZk&-mCfFwMSYnhm2(%t zUixoKQ?)7f_xTXNN>vA5ha0UX9}Iit2MbZPIZ&H--71kaY0B?c!vBg#Iq}y=3jeiu zkYxX_^Y{O^Yb0u9Yb$T&YUyD5|BpkjbbVYFq)>hWs-AhLyjn4`(47aimhqtr9Pz+i zQ&t6J!%C=NfF26 z7%Nzd_K?pQLeQ@|vdTX?U5-NQAI9L-tNP+PSX=Uy|RAy?h;S+k9?kt`8^=@Al^U870Ge;~gKG@%s z9ob58G3xsSQ5xr~EEMG8tfg;(eP_q{eOZj36B7wA6E*w!dfY{17}90GBCHS5;SvIITLZGKq96(mqUuITh zAIi|UhMX?w_JqBq#Hjht1BVMB`D6mZX%dK>Ks zE<|~Dg7Fq!7jaW91WEt-?f_h7iU!fE%4u&^yo8w#ZQ!(|E)APlp^Hs#gzYH7QiAoq z>k6#R4+RX-7bEmhMN~QugG|M53PhZ^h<6Q(ZiXWj1YF7#8i{1ohZ%?@j+Zw5aFM9s zuS04R$AcUO*M^Iwzk2FrU#UykdrIXSRL}Fz1XmHkaGGkKX>~M*b+3HyJ=Z}ex1>+2 z4FH2X;u<~1G>WfwDZNT76|dfF|LWl6fbu1b39p@EJ^20ktTPh~i%1Z^y$koYrZB5zs!25 zW`Pxo=elnpyBELAwrN#JLy z=^c~*VV*0*)C9rM%D9UKf}>vlnStSTqFcEZQIN%JCOoNMYlb5n6e$vw*)IAq9Ms1a zr`OUxKvD{R$g;}9GM0SdJN z*LIpexc3U*5XiH~F+X;Z2BAw>G|_(SU5*0Fw%VFv6g-7BguM|pWop>f#$1JuKjq2Z}NzLd0RN~pp z2V`Ul|DgOz!FdWKL_Q?bXR!rg1ZE3jiz!bmyFpJbPv=*UFX4iQfX2Ev4W>o~@sdf4 zqYJX<750Z{}^@$Re9XM#C?9sCk`jktOxdzG3j= zW6(M14R=kGwFyW)lIh$Fd|=n;Q}&^d%*w|q{eKi*9m+W4J)G|1X84Z{5(uOG6gEKc z2la@@f##J?)a9m%MUD0dAJz9};|ChER%9X2!w))K|5ime%_ZbVqs7gUF(# zi5fk^%o2C_3s%0+)hk)40|^R09C~>Xiw^n6Z@h~2Od?pm-gho^CI^fmteEiMf?Ni@ z2;&_s9{;`4BM2usIwfAVto>;UJ6>A;`|0m=lR2IY?Sy6SrFU>DQHkUbP=A7BQX(8}KB>?V?ScNrZE|f@%ykXNy2X6WOW?wd zt`elCWvmml7~Gxe{*v{sy52LX8A;NkRcd4Pv_@8%8E$Nn{tV2={Q5pK1d+d5-ralx zabJsm+~F*=&_YRFpoCP`8DKxHh}4uAdSVTk1Mfn_sJ}n7V0>_ZG$n$3^Nj+p=*y8! zW{R-=7ejO*9L?TbO3i0URMTK%E=^&K44Fs6r!&Oy0!eTt5olu!DvAiRu{~;aup!8b z5QLjK)2-qDg`0q`M$su*q%!ZI8>k>M&LwS{q6lHDYmt#R(Ryo~&jSV9rj#5u+Nn~yk4 zd~?Gis^EK9XxjV)uiLLRz}2RbY=`8FHo{eZl*ia1O+t!8y)s0`D?Tb`;=>Ij#J(3B zJHXF%*Y8pT0wK8Uqd$x>49=hq=vy^NTwb_yN$0v9Xc65khe|C@b`b4XHWgajQNAyi z=AW8a0b)pBy_A~lVxd9Q{RNx7RwNlh)t?pc{Y{##&EN)-cj`lD^=*7ii-O4Yav}&+ zgxI~&1s3wHqHhxYf$qG3KTyhz_d3rAmKB?6%Uu95YBK##=e+(DHYFO&in@hF39t(>-y#ZWaK<`XM{Nq+fKwoS%T zyiP8PTgr?&U5SfKVAAxgEF$0sT8pYsQjsrg{QfEll$|2w;)cO)6(h)$u#Irn=2!=| z1X?Y9uH7*8ky67r+A4K+U8`O3H}@R_koevwt0fs*8!CrM!9!Hp3~S~_%qRTjX}u&} zd1qPFzmb6jwS2_Q*%ce}m~6**O}sjZDt7)??r+ej*frGO6+MW$ph&N~p9LA^6F zNRdJa*QoGT#$Nj`C*2yIxegPqegG~(68 zO>rSltMDUK5NV9}Pb@8oE#-vIw4ot)z!DvWabxxR!j2j{v_xvtpa1^83l)d zBOfp^Re;)plu0ih)2SnSuUdV@2V)jov+f$@w;giPrd75US^HzIc?}-@-cPfF*>BvBU223C zdZpgEis+gzczf3;=mEWVLVxeF&=Uwjw>-8~tee0d|F%+CB#sS18se$&t*g5<(=x#H zr3`~*~dw2E2uI{})Ywaq8n~eaaZsPif*XM+qqP8~K-4iT^a$PF~xAvhO zdAw9VVZUX=O4UziG?DN1Jb9;f2ZpZhaQ}Ep22u7eT1xg!gc{eIg*@xvN%ZaV{^#fq zrK)R`By6x(fRi}PN@ad3Qr#O)U79Qi)QYA_c$_pGKwD-`+!pKz*u|+p^a_->G7P~x ziajnn2#P;6)L5r=+e=&I$=!Cy9>QN6?@KMB8@_TJ+F#Dz9;5ju*y=bURM^x0~Qbdw8MGvh;C7F5V zj75{>6m~zSeB39_UPvB>j=D}Q@YlHd3Ts|v)EqIPw0Zd*Mris_tL-ZgxH92@mN2TF z6cLTd9umc;+Lgmgy-=msRyPNMn(`-3zY!Fe*R~!T4dRJ*LIM7Qg}O+WaPDkwK-pcb zzd}e&J9Cw$UNx;gQC*34bP^pWHLc(AnVMCrQljwRUwp^`@#-=w1O5F{?MVqF^IydK zqlsxh4Pu>UYd0!fvof>-CI^$JS3RC*XR@LL@)5rLJq z{yY^NBHQoU&&rh%Zg)KLd_D#Ty%%fK&)Zmb2Cml3%NW`j)(2CJlOUY;@MJw?(e%3` zF53LyEi}<-+}c$gqcYka*vdZMiacX4##0<3J%be9yg$2O%gkx52JOt~p6ld_Xpc*q zL7&u)iQ4BSh7!CE)fJUI7*<3{@oRD+-u;q{l`H92sRo_G=fvvk{E8~hEeB=K$uA-; z7eCsT*PCR@=*@l~9z{`>;BBFR|M#fs|3O_S1VW%rpn!nVasRg#-v6ZsqG0G`X#4-) z0$J9uwciv&{`ns%PL5WAGoiAJ1g(0p z?VU=qwO%l4ay(+%!8|Y5)6C6GZQFd;Uwrm#RC(58Clk@lTd47kfkR;J}^j*OQ6*V)5jHXq-i6{RHjIluqfT)tNnMh zE@`?0V?8^Na#X`Yn}Rg7Y6;QH1d;q7QZo^&M+L=9@kYV~+K(3zkzxd7mE-jvQd4KS z#u}5U;iTI9{LsU>)+#7(3GWlP!f;RD1l5_wphc3j?=_wr^Pet*MHGo9Dt*+7Oe1&6 zknv{NC}Q!U-1#xVv_71ChWV%PLS#naph#{`u_SXwXJ9jdrd}=jQdw1WatH^-SV2 z74Q{i4QT;g4>ibNYZ8lY;#MukGGW87${1wLi9^lu5M*YQaoQkYYqZkK|9A@(mo6c~ zu1#UNL=3A)D)oa^q<7B$B9+qi3Q7U~L z^@^aE;JR&hLBMfM-86<&oqEfBf9Yj1KNkYuTAr{6zIU;8+wIeN+pZ0O-tQyw$`J6& zm-JZQc>2b-B8K0tei(T91PA59>y7y@ZkNz~4M*nV=B`NCJUJk&H~``%6-R&?)3qUA zxr%KGcH%6j4!7PtQvD^9+aWmK?9`bZ(@33bw2Up*mfpcTIq-Vk;~-YNN0?#j>{Yjy zy-50D|vXY_QMS@O$j> zd@At!Wxa144hYV7)!8fX<*(mGAX5hKSbP@0n-|>==m_qcl{bJpi)rs;hMyd9i0Jv6 z`g7pE(l?(Gbio8p@LZPXRLUDP>uWp{@UNf5;mnBT;uCwzz011o^sSr)Gq85P9XRt% z4w4(Zl@W-+0ENhdFZ-=-e;E%V?o@UpE0nkc*B8jLVL9(ifEdWrCTzsjf#ZSC3WbH7 zObqYC;e4;VoRWQNJqsBC`w|qU0e{$!M{n^aBYbMKr^hjvSFA3MeI9(t{U20wX${Q2 z`w<-LOD>p6$(7Z>;BQmnFgld8<=phSS~tF|gIRgdDXN8b^p?D1iUe_4k6Dvdj{i(7 zD$hhbC^llPg7Wu2sOI9Od&sA%a}-&3LI8@z;AyV5>-u zQS*{IWgw^@)yZ%SG322n6s!tlD$P0-z}t3;0-2diXGu-G8EmprteBWmamsoOp=P=0 z0uUy^;;B)}*5=VZ`4Ya)Vb(m=7EYwS+Qg!X@(vH>)D7TA7{a8jKE7rf!SD7m^LXkr zG-}qCH$we+)XG|zsSSOj^%6@tLH$WwtlAWBlOEOo>Bl4N{|c3hnArwmy^ae0qrro--$XM~hE%%04L@_u{NgwN8L)=0KQ+*XrKT4S-2A5W(;| z+Bi5fJ*Ida{nQ{~PZi{E7&DnQ_hl6%(^M&xUy%9;&n0LBkEo>O|FC%(H2qgpn$3^u z*;K7_qoWz^=!HQ)o9(o+Y-q`@COz^qsbYf z!77)2-2DRFBF6m_TZW>=3%jgdUdFA@Jvr={#?(j{ero5h3geCY!(Abidt9s=;y!VDZxr zA8t0ZK^O8vC$!?(h;iy0l{&@1L?j5`2u|-hkjK~M>&qvMHOi^vt4Tj|*1gfvTPgO3 zWi0vgx-6eTkh-bT&1G`BEv^7)55c}CTXOC5{d_fZ%OZ!|j<1vtv8yqOQ2wzw!lAlq zVB7e*UpV!~G~j&%$vp)xW6<1+STtnbn=Z-C7hpjE02Us%m71{~Q$L|zepm*41w zlwbxg@7H;^5cFLCQOa@<^-L%Lr4#D={|0l0G4lI%QIREDiF(9Q9$v)z z#L0Tr{91B&A2*A%b`!cpYs}>P1d?nKbwFA*+cIVT$%to8jBvRx$qt8JB4+a}klT%U zc8~feSsbM7`AqZv8fAS6JP0kvoXP(;9$wL}HE;S!eOB2PdH1Hvw#`lJq`sDH|9oj- zH<+lkHtWtEA>v9;0AAoKHUMFt$72FVA>YjfZFYsKkPXa==tfrV?eYI3CC$CR_DTE~ zp-7Jo3`F^VK$}GV9}@Y04>Mi+T07&2C+)q)uKg+muWJOdfk{2wjKu1hM}^SHD3HVh z(SDwgnPm$k+f>iTp4%k^nSbxn1D>zy`~pBKM>9~269rV&)Xe_xwQ|G$`$2zzb8l}y zV1REXKYXLw0p)JK`hG*-|4Bd{6yEI8D3R>rj;|Z$iDcI4$remsAH2=dxdoTje+A8z zf`d1vAGYS?K#Py?d&zx&G+#{K8UM3-b0~f=o?IUP^LXyhgEUz%R^K2Ia_;6UI9%PfM# z;8VkWRX*v8uB(Su#J`CY|NB(V&nx269iUfUZGw*qWJEEfhYNF&fs#WrHF(99gTQ*U z=zxDLQ1OWNf+)y@QO$#@Nc7;6IxX~Z#3L1CLrb?{lZY@RtD+OhQtZE|DrkUy&eIWL z#&RV}m?^&#~nIMbf2s6Kuj~&^4)F_#3qTNzxrT zIElE=ue!Zg&MvyaKHp z$mahKjP)O#;EW($lPKaSbkF{nKZAIZ0Wizie!ah$ER|f-Q@VrN6GTzl><6;nu%b69 z6-5awqt_G`qL?H8!2)EVbmFBYgcZ;bR7I~uw1OicWRW>i1tRzbNRK(PCA7pakS>u5 z3RYEM2%1n{vYwYMpSr`;DrjTOAi?PWyhCEl@qO(zOf4Srg>$-sGZ2VP(sK@AEpV(R zo~Ep(DpEhTj_pz;o4m`_azW^h2hL6aO)f1g67st-#7m~xaxe;*cJJkXcXa+G^3u(#W8E?h@-B|sn6s7}3f16`uJ$Ae!ffe{74VZMx`*=05)%nnu) z^ib5A_tu6=*(OSn;*eUv-}xnz(dM>{ObXw8Z}kdyFL9Z?>i>fOLh5%T&?(C6SGg@1 z;8O*^W3|PgPqI*hVNVZI;Pu2E+-1HlIyUK`B@@8}hxYbUVm&S2VsppEiv!H|k;%2c zA4sB;4)D&D02;Rrk{YnHatPn~H|3HborbOy2A(`ylT8M`P)7ToPJcDfFaOwZ-CoX> zn%M_^UD!4}`Lc#df{AL0W%N|zw18X;-VGAr2YCX8sq^s^`WvcY%pX$`2UhmaT(hr# zMs~VcJz=|TuxRr)hYS;|GB<2wv~nsZMLgG3O6Tb51!c1H$k2DTGXK+G7NV~-!J4*o*- z4<2+mnTY)+br>IfP>L@8%K>M8Zi@VkJ;TkKUz7GGo4!IqLObo}Yb%NL+eOy)BD)|sjs(1lFlNL{)!tGiegQ6WZ@I+=#7 z^|r4D8w;NwOc|LHJ6tF>DupJ85Ht3C`!`h;Py zh-8XWkRz}!;K=^#amStx5ro5X#3doRJXio+egMmX0F!|*@jcaW<>ADbpe0>U;TxxJ z;2h{p$E)$v>JO~7Di8jFKEt=@9bZa7fDBCxyX6bMI&}LDL=ki3Z7>=KSLWJm(3w(K z?)9i3vJQCUMC{^ak6UKBzcSw0{G%(5j8QI#1Vid;lJJM(Y4CWt%{f*KE8U=Q7f2|=Y;X^+sRjSGmr3G4h9DZ|+AiyHk(4;)p zJ*>~hxaBU%*h7(ymg!!|Z4;u|lKPnT-TB}JX(U;KP!vPN=ZF~GMrkAUju|eN%?1_B z@^=CV)5Z)py9fS2V&plcNF@h*Z;wE}-LOS7sB00dp>l?U!uBo9U{JJ?L|?J?i>(z$ z%KS-Qz17*;z}rlwoN3d(v};J?x|Sb8Go(qkxQijlBX1@!+$p&9+by-Qv2dz-;);21 zAJ$#&?oD7tuXc`$kU=@*A;@ImM{Z`D-zQEF4Bqx`tdjmJVJ%Zt7@nhWHYQ)Xt13%{ z*lY_WR*mkSNuSkdxD(b2R1cA&ECLdT)&gseN}JBu^MOzU7^`IHE$ATbohe5YLy-f( zF%rYuCm?Zb?q9KA(MMBBX3XnYfki83!+>}S+?sq3p@CJc@SqH8QUfq=u| z;pR0a2lDRC-&94w#a$AWNrU8_34F5{-w}(Z zVy~}=qSrP>Sb6}zC@xwsK_udaaw5YAkE$?dA3mJ|LkzU@PZQYB4CEF!d1fb6 z6hRsw!UY1R*N?7MeoA)<5L=VKaiPlfihYbl(Lc~NxUVpHr}+luVfqj;rV`Do zv4Za6$qyP%R^dVd4XF2u#ae81gLi%R1 zRJcfXMl@-zhNA3j!UcKLsSbX0K;IjwCreg#5Rg-i9H%-pNP&jccp)>4LoNm7I2g-3 zko#OQY^?dL19k%2hGxlI>*7LCa=AkQV-QzwTC>=|bW zSQ%d`Qe^+z9CI9dNfc%52BoA2EuzAWyJpCbxRGh42(%E@#^yFY9DE*V?s?}2?H6yG z{d^wbzuhNZiRIC7--SneC_^zAFIfohx`Cd1zJOM_=7$l!{{Us<=VFmM?l7|w}$nX|bJ`Y{|OXG{YKfXb-9PED+8 z7_vY91z4_ElOt}nhkf!iy}*WxqIb`6KSjAF_%kk6TT=o#^E^019&3{Ex#^nP@QDSs3? zMS08Olm^ojgyZ1jNgGTU%2ZjSTzLW$%<;TA{6l-X$4w}kGyRi}rcxJ2toVoWm{}l` z0eGOq4WAEVYMF-b7Mz*JuBZ4aiaZ{lcPDfOMX0)*e4pkwocMc56`Wp~ zQ4bt!F;W@XZoCJ>1(>$US9<-*qY*NN%9lt7Ce}Rnc95H#GVzvaLh#^R9bua4sE4Ht z5<*f_AGjMW*8Dta#+%F#8^4j(n1ux*(9wT6vhBly@+Tqud@5ajWPh|ytiALa*Suoz z8Z^ZWYI5)EyATC5-Aalh?USwD0a(eQS{qV{a;ctnDK{)(GpVCW8xKq5^ogEeV5VYZ z6vUMSoLW){rINv9#HN*vYibYDPvSYeG$Tig&_L*5VVJ)RZJ9p`+zBxHA9Vv1fXUf)B$ z7*#4?bs}XH!TP*JmbVEv~V(YW{if= zQiS#~EZrb^B*=a|c#Xbw#hwJ_`Guvdu}114UE9j>8Ry z$7N0cjut@=TD-MOl2IVIAqxD|;3L}RhI5xMYBXkZc>+0V@Z1vu3XhEOZOHHWg~5&~ zQ31v3m|gHYhv3O|Lg#tVP00p$?JG2Xry2+%gFl^eLK58@;sLp&Wz=z|^A0mO%X~*b z)@0qx)f>q&yL3ui=djyTdv#rvIci$-H^6{kB{1eciZEn^7uGU6HHvGd7JcmR)w2zY z&@~lsvis9BDWKN4kO{TAL7uc{Kj!2N{ zE^WpmDiD+5L>p=ySX%OZpekS%4b=uA5M<6E6mNif$@_nX7IU69KOV_NlZDKInw?z6 ztI*;C^`!b|I1i#{P+vA&nxXb?*-GAkjmmDXoZ8@#E(5q+I63EaQjXD2$2d<&+UUF(&&Za|2efKbciL5@=!--A85IC`YJOC`FmuA?mI3cPVg*pL?rio5T(i zxQaahEGxAlykk()4rAUsDL~zlSGr%xSY2jHy z!%+)3lR$4>vg@2*>1=5vCA+G7=qd$G2;U#}2Km&re1%*tO9pz1!Jv`5v>-U)R}U%7 zbrN$=;CQJRhBq*t5|QB&J~}R%mz+Z`2o2f^(nJ)#O?5XhMinB$_%rXY-dxR>L5bm3 zE>ra=qjehtRQ8RgaFQxlCDhQ%C5#)J349NFk^)NQIJHRKD4Fg{V_K;>e=QLg za3!5y;&~k5O|qD5Ts;(G6FFHLbJi7tV#w*sb42yo>8}oP5mu(;*;4P_+QiMVddl0= zO8Ka#=VkFtc*dBFWJ6Xkbp1>S;OUSdp<6$VpK;lf$K_wry#BD^K21!v3F8*A)VD)7 zlK>`Bwkof@qTD?r-Ba`0B8OdMI7?s~2HIJ}u=Fg#bArF^i`c=<^-lirSkG>16LAeR zY3N#A-EeV<%o0T0k6G6`pQ}n0;eaMxZ^k_xwXp6=HVql(hb3an{%bF#s3za#!uik4 zR?wX1}My;!?7nO6;**vU?e6 zuY2KbhCB5^-U!8x`Uaqr~#u6)!D;|ST6xghZ zs)yr$S?vZerF$%G8FI-&{?&cPy6hg9Ha3k_3vQxgU&%jI)VpuumVwOCwg_q0kkL+87QU$KNXZyu zQ+Di*%%DD~%S*^M>)>PQ82j!JeQ@cCrdIE()Q`B^o{UWt-f5!5Fv&itbfBxT4(C0{ zFzV;&mU%Z_FUnmPHe-3D@(uNTZao%9=bjQW22;oQKjYpX*QUG<0_oThQ=Lg0YR@oO zc~Tjt^K!GDaY?UR)@zk^jrO-AKLaR&Dbb2LpXlq$6FJGCxEdusTZ}Z#o7*xP>%K*I zl;dspPunYHAl|FRV%LM>vVhkI9bW$FYp&N|WPWXrN}@x-nb)|f!1iR;dZmZ@1yvsP z%CT!m1oIx9J8U)Y$B|79-vT;(%%yDmspor`Ed=_88Q2UBY}7my;&07P`!ol-HsIXQ zvCKIYZ>MyoFV#rOs1ql{N0<&eBvO@FQe8<_o$km9iiIa=LIw^3nNBn2sFyL1~P zilwyE_k<6g2H=&bu7ir6lf?A#Yr%Tax9=iDT{;>`$!*s`)t>P}nd2tHT+$YprqqFz zl8UXTRynCE2x8UsDcQ(9m53y6Jt|)dCHyUyaIG6HOpSJ^?eP*0tN98q#YJ;Hj#Z9n zK(;2L+&yzQb+s`I%E@Xe6_tqx?~h~`9`ErUKjgRRve;9Xn?H8$O*~nblj*S-eEbpN zYSmHzNu|j?0n;~P#unQF1^gpKLH;87b&8aK=k7$0n_^?+4Y4TS}2_hW3IQ34%Z-Rx!~ow z8QMBF;>0EJod~eBr=0`2N(XY%e^KJHfo&)~pM=rEmDK_m8rqC7W|%kurDz6Eq<;Yl z399=w$j}QrbgLoNHc&dSads`0gifGnf)&qdjQJHGH(z$r@zgz{{=q=K|s1JJ%=|ZxO z&*&t2nt5ZKI+Pd=F44Yoqv~lk=Us#SW`$r8%wx6n6c z@<6aTrs%zJBn6{Gi}b{th-kIP7>KW$tG}$!poGZ$Vo$Qo=_j4AiO>gsM;hx6Pwy~5 z*5eud&zf_cr|{|IQH4D`1h`^6@>K0`;R+rGP)EFH3H&k5H}P;5dwv0D{=*t|?PM{F z{^(cQ(jS4oT-(rZpT+oxI|_YlSTbYYT1N-gC~T#SjunXiO)%VR0&gj)lem?NY^PpL zlLUX^<&5O<47z|&?^7LIfw;}Dyyr)PP@4gC+lN5K5@W@@tRTAv-V!(oLpW0fy~9ge zwqLp6$y6-u=R`QW#}hKLiu_-NwsD3e%idDd_%1EYPY;NCKpaWpU3_>vP0YmEnhN#& zQow$AZbb%80kRIH;*|=2_u#e2PNQmL?vRujc7;a9$v{B|T@7G8i(=W(nTd~^i#Y{E zd%ZL-)t%Ydcpl#~*%#iQsx!O@WS!Wp3MT^}^lJ{vXfSU?R#SHM;sCnUxk1}H|2P++hzYyO8hr1^qLw+4MO^pE1`0DB`)-Kx3f(c%r!y?8Ti?bSOB!}S5=7U5Y)lWg(k^263$O?;XMo!81H^TE z3@zIp+#@t=Q@oAq-*J0JX9w5mMXe|M+0kFw)wcNdyxG0VVtdjucv!4bM;`ZhLEMrA zf$7F(PRZz;rZ;)^kJ8v~2c{C8zP>||V$*H@2~WAhN24|Lviwr!nWQvJ4qYm4ph?Y9 zeH)yrQ`FVaB}U434kgK%n>$5FvX1RzwDm8f+XuMh*;#8i(ZDay%X(^?3VW}O_IAPL z*ZoFVfsNvL%pG3Hto*3TTn7r;VnYYxjI=pZ4dnpB9Wd{wWhAGVr^zvj)w0?nNqxP0 z5wsLDuJYm0d_cGZaW&ucSxQOWlmp6bxO_zwYeD~Aw+v@;VASYIvx}yGnF19Ax%7$( z@RqSjxTEJJ_}zr-$qfU(Iu{9QVnc@wtxvN9@a01ALzw8(f9D7lZu-Y4p)OS)ws5a`bGjH77<$Ge$t<VU>4br>#WE(%btM9NAu2(;EZVS8JHhg{m6)g>?IghCdb(6M$!;y%W z*;vI6M+1XT#ti!sCOdp$0G(mQ*$r*ouNdvb&oy9mCc2w`Bf8z*g{3nBf!5M_$Kz4} ziPXif_-HIv7kC8Z1q%8RAQA3wN2RdB4UPAR_k<+B^^KJfwUel;+7a~C87S*fK?9j4 zAMqqDAL+56VlVDKt3WjrI>=hD2acsOlc9v8ZK+k>8r zn>u@RopGLvZE;gUCAMCr*H)`zSr2ARzwfg@dmxa~n%&8TOzYT>S2a%spPUo@4z5&w zhJ)DNg@IgOYd&m(^K(q8(;j!8A}yv-v`8xf9GNa+)M#qI10tT6%I^A=S4og4O)s2`SOERP3G9-)c*W8V$T-8nrLbEerhmyZPmY_qck{D zvt!Qyaap%(xE~BB{K8c_-@WGb%=eq9!FV!(b;{srGxhbNloi+Bf_O4->>Ip@=d4|v z05qR)$!0OfkFJ~uPQG(}x+bv?`JB2(;Wn#V4ZonE{rGMg2e#w5Sao3QuSJi6!-P0$ z9u$htfdBnPBB@w?b{r-0vE_MB8}clDRwugY{bW~ig$li*m|S}=@)bcEbLC$xhZ0P z+$%OKY1raIGqsw9R*Pi<4l5%(NvpJcbnzrV=^y4Qor7l9#7Bc(@P6avy0cALWcC%) zf$pK@vhj*k2kW3CU- zD@D8`o5^2n-5m>8Bd3=whLAA9J(W@Ot<%t!poYbiE3Rp`NP6@x5z(TM&{ODAP&;b z&+Y!xI5};S*i|B9twzb6w=AqT#`TL=5Yn@rPAs_MO89^^doZJ6RvPmf!}eQNRlQF7 zTWvFx{#fac>N0^HH+M39CV;4nim7I_??oFtWUAUxXaU2qs6qs0b!5Wd>bxgDCZ7pU zxu58C-kQ_DS|rPPpHJ=Q)L!zTt8PwS-NWUD4Ky=-S!$BT-0^D5R<_lNIgJ{lLk8(ue=m_mJQM#Mh(#T^vx3&&>IG226 ziqWmsx9()+_X$yX#rn>z?GIj${(2@B`@=UENdj~pb=#55f5MZ-SyjZ)#o$LeJx-SG zIWNu`9!6&T2SLw}jXAO^JY$u_p^a!8ElE(csVEeqJC)E_bg?}j9khwlr4U7?=p*TJ z*}!~8xjPCEZ7oXHYNayi!lo z6vL_Ke29aU*0Rkr>lI*~4)!N@P|Hp+pTQx8$S?9Zifn=R`n6$t zaa(t3;*Gj$rQ7-LQBBkX*#JFodzbc%syb=lc7t7%YX`$U>SVJ*(_zzae_`m6fR<9&n z91D08v8g3Slh9#{jIeb(nvHn|lmH6s_sV(*YMD&+FI>*sYM4#G63>MvgF-2>qTus zeWki7Y08{g_jG#e;W$VXCafK=r-0rHo2&>9!+H_LTo<-6WrloX3|cu@F9Rl{$>AKN z%QA728dRfz?NuPvBx`nG#JR({h7Kr%=YN8B7~lwJost^)3d#4ce-BKwAJgdPBrZiZ zj5Z>{zCWSkLUfDbN{p~)b6=_S-}Enh)Xt$E)GsJTEBQ9wuh6A&aMZ#qJltlLz|sG> zy`ftrb+qT=pw8NaU6RC1fYctd)8#sLagDxinSm0Bnn%Wa6%o~swISF=?$#RhutR1J zi1;#){nhF#j~}QRom^{vER-;IeGhAE=Awe_4e153Sz44u87M75m1?SE3D??JWh-W@ z6)Kgxwr}z^!cN^K=5D(EweopG`iw8JZew9DObv-$VDmiaqrD%U%oTYW%RBgO9zPGv2TnBUY(1KGGzM;y@#xe*i zxk3MmC;H5#4}-I?4GZ{H@-(i7l6nJfpuxoz1(4lYXJo!|l#mowqlTB6mi{-6ZF)h| zW)E6E`9CjkkgFXV70J^y?b9hnAR=D3N z!&=krDWY&CrRA)T()ClNdJHq4Vm4Khz#~88DlmTmF})3SB~?QzmR{+hnj@<%gH3Qf z`;NiA^*M;~e2xW@q8xq@t^|CosmPytlzUqu&~S&2@jD_#GRH-E<`e2Imz1Y35Zds! zouxZ8^9d`zQqvHGO%x?!5rw6w~T>F$n}H+`yMsvr8A>Tpd=XSps-DJ0)Z zuYUw@0BgVeJs`C6g(e}6Z7n*ee>T;D-^vbyd*#+!DoeRPVJUy-!7M2wgtit)MgyO3fjQ&&-uCp*1tuY|2XRBjVR73*)t0kAnY0weWQ zCQv9HTb^tWJ%4IR3qk}>$KtxV_>*bHM;M@r(cQGJ{t=^0;P0!UFOiouRwuQIZp62w z_^4dlQ2632dH2O{xF_$(ZQ`K)N>OGb;#Rb*Z_=oSy9TjidB9AT1#4|>tf=*F*tK=~ zN-4fe=y6N05!?82b|U;~&*gDBYjv$XAR=IU9JzA=0nB~##C=ifkU3)&(t=Q@4!tlt zA)`N8-PtD7W>L}=wjHVZu#(i}m@KtVE-RyP6*EjeQS;89(b(qZr7fH66xqPjROAjl z0d_|fAMguv%P0juW8_?*v-XHJUHK=+2G_z& zj}9A_o|#S=p&#YbJzyR==PhiAPgj7?WZSkwh)7;PDs;0#9~l@!7}wE~8UC)sMGpb0 zEj(+P?6WTIH?{QZ;JTpFeEcf;>-yRn#@cnF{Dg*i?B_!pb7NJy|J_X0Y^xDDrWTW` z+2ts0kp%P2Px7n1{5+_qd$p(Wzl|9F;EflgS|ewab6E^DtLl}&sQzs~h-fdWbY8&1 zJ!{zVqzx6}{gwf1m3o%#=&)%A?VpA$Wn^;%sLX!45F*D5>vz0fjD@>&Jzk&$`N+*9 zp|amN{TV>?(KQ$5APxBQAl~FATZ=_AxET^F+VQ27)2Edrzj(ZjwF>lQ+;v4C(bLPl zel6!lu4ujVu=xO2<+OfdxO)-T*pW3mOU|;xv;$eylu$jIYLXlBkjcVtIVW(9C3KN) zap=3b#Unxbsj-X1+ohkuHB|~S7L&PI{DMxO3RHjta;_vROUGEZQ=nH@Twa}ct;mS_ z6TX?GA~c4F2&!rsK#I{wyLbyvj%@eupx;=d1?^>^Hh5WC2g_)|)hiJIoKp<`RGLg)Y* zAJ_Zcy}s(@<6nVGmEI2W_R|#NtZ)De$r;&h%d7xiDBhcTqZ8PvL{&QfAT9t!71N{K zcqc669Xt0`qorS?er8kr0oG!Kb4+JJFpeR&@v7JzrR)eMsVvz=Q*#OfgU&Y6#wp-a zd$Qr&M#lH*5q#b+ReyW{RV#j?s5Z^V+dcl_Au+a0y%4<4u(~kZm87)2@s<(}TdC*i znR+yOsHQWR0Lete%F&yY40*&2QsQ@W|<*`GcK z&DMcK%V6P#H$A`6BenQy@-)(4e*$N6Nx4t)s_wwZok;@^tYv3~XD1#_YESaeQqDrs zTA^I_zufKCMb1~8gMwQtSLJc$&q#deFM*-%<;~g*Rq`fmcFRhE-sQ6s-j|gFw3=r|UpB1T~ zzbpmaw`m|*y?mo@*0W(KHJrw^v_)AwSx9W-g$rKUPiI~nrVm~N4$NNWjuB_&{p zrW%%v;9lA}({O-y5TU;&V)dcc;wuSTdjl(rik%}M)VaTY)li>>#o`mDNaNZ+vu<5w zqSJM!i4_WlQ3%#0o!SQolNvX8$;gpFCkZ=@mn#ecX3P7dp9oyht#^~q#OMv=3BunZ zyI0yPo5I>7G-p0XFO)Sx{WBGYx%uQ^R9n@M+0^>pT_v{?8JvQW9-WL^etpUHft+fQ zyQw45>HEB2qc=xwC+m2m_CX4*6B}3Na^sBmEo%ovFW_J)JhDS46loFUnQ743o<%K) z{(Wu@X@zhUupaq4Ehxw|_@HQUSxXTO!K^s~(ZiCyiUw1^s(mtQehK7nCiI;7zOTB! zGY8j3`X&7^Co2dSi`Nu+%VZqeA)LiORw%GuEC4ENuimpMk7t%-L!oqdu zDXCfhuBQeOEK0YWLL~DsKRaWKjG%~^P^qNqJ|R|g9Gw_Qhgd8Pin6gXHa>-OAS99712Dvn-6-tQ*V?n^3fb-)W!ci@?73+N9=w-x@g z;=gn?a^4l?XsRCxtp!H)cT-}&Q}co_Ktw}Pl2x^%9Z;M0x$Na}NwEs9(<(p5!ZwdK z261qV=fm)FUQcFA-yKbhe`~2qo6C_v4>{gsy&NZG;IBx@W7qkT94~G63q(zD_1)wz zoBog6)%kyK)@b3jcHfXl+da*B zM?kyHfmpyiaJkLA9&!vf$gvoOppQ5CT$Ce36SFN`NFgycBm4Ee?S7sE4a_~qKPCaL zyy)2_{h2K2yrKx4!J0w2oFc- zqF>g{C*RT^4ErdWHzqp}00VeXm3fjZ;M<+^H8k%aTs>E-*MHXkZ1H;!&-`3YuV1qB zfAy&l9DXvG6_18|OD?jYAsAv$yusNrvD15!?XU>V!R&m)V}KADJhm8s9wba3QEfVr zuDPB?bD-(IXi}8GM{qmigfXpG-r40gw(c65^M5}rQYLLcok^WI_Avv^8`~g?#;}!v ziASnbKq5Wi9#ujOuqf{{jpD_O0#7RU3m8>SrP80xFvYZQG^VO#|D9|#Y&BDw|7Dtm zni7N9r+WHhn@)%osgTVuDbj~b3J{vZnq1)t_6GxsB5H>_Il zf+<4Kf+>P=9TQX~5~SfCLms(T0evuvgl1M5p{5H%rISe3RsdU|*2wazXix|(hXNJS ziG756C1mS?s`(Zw=C_0$`3_;?@P9FO56q!J-I}0d+qP}nwr%^wwoh!^w(XqQwrwY! zucvEfZdY~P{uO)GyVic6K>Q!=Q6<;P!`guvxsFSdGGa1skt%XlN{s?2!z_y_f5wJl zffew=#4?xi|EOC5kL*myYS5Grx*-TcG-aqfD@0C63;}>G!5CyHp#d7;!7RmX&Z?WO z4w0j}DI!4o++4yVf_GZtH_)o6Om8!IO!|aX5kxAfhI8O+P~_=}1K6n54KJ1Ju*ryC z#MU1)dDP~jHs56?IVd7C{2t6~nL1OwFeSubx%Q=rGuua}313gOL71Bs&8r69kbZuK zg+JZdF=ou}Z0Srl$It87x=0AC+_=#v+ZM~F+wSh&ZI2`PotJ|~(<6zTozHe%oR=B$ z9sc$vttl;tN#m%Un6hEHyUV*lZdX%&g#XmHy61 zilc~t+ZyM7XbcE>Oj3g~`$y-Zn_o!91SXV#i($|g=3t$GjWTHY{vB_7Gve3BCXOG$ z1k8vn%xpWbcum%j6I73*0&4yf)9quyM*su>EBfWP9>RwI5x09DyjgJRZWf?$T`n9# zL%>?#(kRU30aRx)*26Jn$@mE(DUcNv(tX@pg1nScI3;WXh%x~~!krYGV*Nu|O`n$L zX}mia7hr7D~{h16yp590+)c#ufPSY87UC_AA#%m zKLS^((Nv-8)c+%JVOs{;U>I}eJ^YQ%UA-jIqk6o4IEHcAGm`j;_3PumJBz@5CX4)iCGKHx;!mGo30+YJiVhumB9UPSB(cQT%I-3jFz{}fDSCO58GsF~v z1h9mDn*DSK#+(Nj=Ko67nmic?eF9{g|32_R8F`6*2eg6dF`@+X-1b+&*Mw49yWRHS zZ+CD7LKstFs2`O{3dk=>;equ@xrM0&pvnJaMWX^4uTXy>Orr^K2tn1$ARp?FRFX_M zeOE)iYm1PBCFD9Z4ex{jRYEq;WPl@lyHQRui<@qTN)sZ39AfGU;-P)$WVLG@tJGm; z_>~xE^dp!J;n^lWf`Fk_?<*Gw%3xFr3Ot@`A!30=O}r zwGKYflgCvhkbl1NOrQT5BE|Xs)&;*&(c#}CGHAZ;$cn5nP^3T#gklm5vBXkyp9T}w zcG_NYj-wq2Z4Pnb^R73a=WhjcAxG;q3<)vDw*f_?$Wm%q3qfY)LWTUAVi*RTP@x6F ze*Tc$SE|J+U>Dw|1PKcpCLlZozj@EOZ)~kXcDaxlz92yJz-`T`H45vh6Q_m&d0X)D zHz_-6N)%5Rw@3*u`(+Q?YZlhj$_A3e_KieC1S7V^J4}@>Wi3ErmgazF{i=P6AVsWF z3IZ(KMZn=D8gxlwlY2c-NOpI*BRo`AY)EF5_VRmqo(p7X2m#7axgnlm*$}1g7-$*I z?v+9&(Cn$$R#I3Jc~AI{6+s^m8Ii8_4K}mTVSrtKAN++)vAtDGra1b$Fp6H@sXePX zyGRt;59J4v7+6xt&Yt+VV-gsK8lL0PJ9-i|-(q0krcog(2YMR=E2>(gb&i@p1|pY+ zg-M_D%$9@50y;@n*J1EWs!#9FgXFOFEb&2hzE@|8rK%Sh+FB*d_yFL!c&vqnS*{{% z?`V$DtBK_-r7(ypss`O5C#-NRzM*yCHDCqsMCPjd+m_$w>q%=bFte!ysoAUu5vH*# zEodN&3rr1m$oe(QCKQkK!2Hm@#E{!8Pn7t=@_zU7Of{VXT3|UuxGO?V-r&|5f)b$E zZ>~)gHQ0Pa*+lCPnU}{KU_SVzL@bdSo6tuQ2-U9x@Lo3A3NNO-*>K!b7Y}BuqpZN{ z!v^57k>)=;D9+7d(QUzbJE=RcusCKQ-)6yRAqp=+GR$DpD0?n8S&$5e!PO2{U=?`A zbTS6Y829XtqJxRaN(X&sq9Lrv5jg*kV8_y6nOat$bQu#ECN3B-^I73=jnjgTLD@2+ zT&EO2TLb>%q&hyA@fLlW?xo5Lx;?D~MnKtmts$VKz_x;Wo&ljLycLV5JO)8`H;sCn z2>JC%fx{sk1ydXadIETV><{!x-lplwH9f zII|e?#-d=C<`Lo~3av^GI-fErtMs&X>$2+Bw|i zKsfbnuFR;udRbB_o%2MD&mpp8A5mJ!KxAauZ#1mDs$fyoh$*J&XRQJfr^wZy ze5wM;WWi4MpRLwnc>#ae80g#+5YUdtj(3UGReF`Gf;YF9c*{!_>-m@tRaZS@zrf$A z@g84iT$o{_nCl#Ju0NqDBlUF!La+i9B|)xsNT-&hyWnvOMnw%M$MqepCsebfWI*IS zvmqvOhZ)D%HdYm~V0QJ*y|(0(l9YF?JR(6>O{^yji$S)hvk;($RA7)t{%4d?-Rmz9 z?ld;szjwek7ON_#x((}4bKfaLD zi>!YkpQ)Aul{Sp$anKJvYhO4vEG*=A9&n@#hf9zOtEsnH*dXdv=a zzW=F?rLs^k5Lh||qUVqvuX-xnhHTr6?&Ab8r!&1}t76gKT);;_;n&HB*uh0Jjc%uR z8`o~9l+oq)nIQN(#zA;EgJ=7-Y@Z^PXCIi*Og^1Sg@Gz5V9ESjGZ9P!CRfda-=lG6 zvB^pE9a}BADG@F4EzvD0U@5g0O|B)R9hRDpkfk0b9?}`^kgnd71T3?NTkT#&Z4bJC z04(1EKp+A)300hpURlp>?bW>~degSGV_$qLo{~zB>_h+$B%m$=l!b>Dv}L4&RXF?u z+gOc7g8+WXJK=dSkzPN`cRGP>)g^0cOhdhRtOp+8REb#VMlDQFniQ$R+6d1C!@$*K zo=&xQZDjz_5~OD;UBMEzyD|7=M_BElO~V-a_4QKhwf>yIV|{Q=7cm{AI;+z(^&S&) z_Y}7~*C24YG6KU1>Nu&J?2%ae*pycQ15RH9)me$1(+f>1FDoq>u2U*99M&`vd%{A^ zUAhclDFklVI!;zcmX|YX=EFl*RpfKQ0jFaz>SJ;1dJ5jG8F6G&{kS_nb*aGkdqKe^ z;hAu^QB}L)JaoYiE)rKVX$R@tFeq_MMHQMZL5 zYa_6kZ4^Hk?qw_a6O)~lM1v?yUzGSSNaY>^zS_RSD^ay`$^I(UNiLf=U=j`-aPEE((U0Ya_g%ktI zL98ED1^8+?Dz#e9v^XdF!jMYV!d6DMICBl`F=wnqOp}Z)g0SN6j~fNZi#HcZ2eKx% z*Rb3a+-3=SpA?>`SE#}sdf^#Co>O573y=oAe zIebavW-QsBF3j5j@N+2p2>%40et_|@8CqiI8f#Yl5=|P^Tp?8xVn<1>(E(wIWh*-n z(I)*f+U^?Your)Bc}jq`=P(GY^2n0U-vma$R!Wp>V_pI}-ph)Jai&Y48@xiW3hJB7 zH^`}-h50Twzl}dbew%JRvg9+%Q}#=9f1FkIKbk!+E*MjDK0~sBplL&z5#e2H8V8WI zAA8W7$1p-(3Rn{6co|)nwmb_Oraj4ZAx&qRn7F3P+?EYx;LAmCmQK5%rm#a@cj3Nd zQbUAt>-Hx@sWt_!)Z}_g6#8-f0t)KX3RY7A5Rf1n>(JYnffLUjg&IqATTo6{2}GJ5 zwc(>ZV7?qX8_fy9-gpVwT(rX+o{ZM-TKe+ zeHV*zd4+*I=KaQ%@1W~C|EOKa1;8n;I1h_4lGN2GITz`u)-gk0R;(!z?9`vLT+TwT zf!4FRFjL1i>@uw$<7V7s$ROm%pIGTJl+m7mw zQn0d0EVLNFaHD9`eU)1|_-(_>pDU1c-?z!k9ceX4QLZ-#C41|?ZHuBK1BsoNf|b3i z6_!vUd)X92g{m6fXZ+M0aFR~`z;q57@^T4c08bvV6vDuZST;7p@`@ky8=Ty>n5Xy? zG?FPMdw4ehRq4;vKwPL72A=jSKGX;Itjb%-&qq$_W*tkdstXpZmvgADH_9iTd9(N+ zp}-)bDETv%J`pdnB=H zz=#F?>w<4Y;TvVf#k+ZQG%kCI6#01uj2u<+FGDN1fqT7QDBv%5oqp0wJ%3piW&}7| zvI`@#s<+!Ty`lgkN7z}PVgbW$y>kC4Z~UPe!sj92Vh0nJBx<2z96lhfM$O*bBe)9? zH&pPiQbnrKf>E4TCBv!|>5?hNFcUu)>DolVNaiRuWVm!RHS%f41uFSTtwd;7H%%+_!(lb0lmY?13M$tYoT>NWal* zGkwfynxGn7hr-BG&Ma)jGuA%^MdvXaUx0b9n753|zDDnqzIpmB$@0q0k8J7um~o6z z>-yQ?Q_$Hr1?zY>fMO4SHi_sBHAI>){J3)o!z!_z2z4i6KeS}?d=wRG;LvOGM$tSw z6_;~!Gr0Rd=XO_Gw*P7B|L#%!z(4L%*{d5lHZA7{vdLeCvY4#*Nx(^2U$4NG zHa3UTEX0;%)TH|P>M(z_)eu05Aewq~{>{K9C9pQ%zPX*kcsc`sK`9NdF^R=Q zcwdjgO&jtP;u&8wFdFeVO_`2=S63e|QINujx)MEH(h@7&1TXmfzTPkW-Cpooptw;i zkCs40@4*;?0*gZ$ic;pH6N?C#yr->|P!C@q5<&S9{4ZDQN=q8kh zT-Wii^3&mQ=dBXRq-M043E#eA)!^c_Wk&Y83BL7?uN;-)lzxE3#AcVuhs}CXP z&p0M5!;{x&I%$--_3q3vkQt>{9z)`XmO28tlA@@HtvM4;->HG6B>;(2AOW<23%knz zA%r_kEG4JAEE%ZM=Xp<=L@Wo{x)`iM@w9UpH0B0I;!6^1u%ZVEd+!xnqmi+^>;Y^N z@5Yc=XE2OgBBa_Wde4*k01=uP!{T`gYN3L$sUJN;=pWx(y3Z}N znKWXVDc;CZumNf_Ci$hw2NfO=v0+Ubxx!q}lwCLeK?{di)X-6(9WZv&h`ZKI5D5$F zH)}joX04(>v~rleIRuIZP=E&P*d_X-peyPB6gW#^grb{&Ntv`S`7h9v9(_1H~7kPsH&FThu`gvNCUhCnKgk_4R<Ys z<*ZENhq3%H;B=ckveDxZ-@RnVwagpNIBo3B5iU&F+Q$-*0qre}jN5}-B_Wy>!ojnlX4+X_EAGQFkgvam_2sAl7-A{5aJo%RJlH@cdCwYQ8O+RK zXrF-L=?0aCz1M^$1S^a`ykP+jiP6!>2O(#1*K9^o5~!>W6ApHnXDe-{^3;}Vo3oa09yGl zdT{!H<^|M@;NZf-&Gw{IEC3Fce_^Q#$Rx#j9xh2E*K9zv3r!11;0QRJs)brIvZWb! zqGpZ{c{8@?r6uM^xRp|zB_E}M@2XAq1E%P`Cj(wgZ)H)Q%FZg}s!?nz^N}wzGK!U^ z>{sBys5n+dt6_3Xd%rUM5&kh|QClht`$QC$RiYhihfo?LQz&7qOgi$C&Bqgb(hyEo zBH}*cj(E;%2(__S_QcvzlqAxB)X0^NFG8=og(GzchqmlI&b90V@@2_vm z0sz$-)*_X4kJV0|kb$+(HZQ#UYp4vmhI?p2IpJtrbx2p+5Yql0t1T=k;RZr}s1V_y z0sv>xEDN7;I3tXhd&37~(I>nAaV2w!p|KYDfJ+p_bAVceRaT@VHk=O~$%}-*^7Ds0 z_}mOAc^{T2bh*qxD6A4&3*G9${qH?CM1_DldKauK8bo}Tdua6DCMfVrLj@Ebtg45i z*~h9U@)eW=Ra2PG?m9#Hsj^o7#(++lt4?!#F;lRRx(KNXiGxGd9jw8~ddUR4dN)4l zqzn}d4>DiMpEl@gggxDs7$j7f*`{KSiYoaTOQ=E8`r$x8^B5&cObEP)TiIndKK>at z%2pfK>AOn=t*6mLw02gY2YAS9Ij|QzI6NMbmmiPUg~CDcAEH*MeX%p5UAzD@wirpX zcA!AY?fTZ!{IoLuZA&{NA6%HREqHzdWYtwH^Y+N(q0H;9hQNfm>HaN)`pW|ik4 zs~JFK65N(Bt=dER6?Vkcbj+f!6NTP+=)`{NM9XV{>w`9?HVcBJo2d$Y1C|AHO*S{t z;x80VVL@9dRU27%0uA8TY3=#OZ*+Zao_zp^J|1Rz`OiY7+)V#X-Bwf8Wm`EMEX>Jx z6k|iK-qRTLQsq%dTz`z^M7<9+xbOLL42=Gk#40dduX|sqLTD3PsiwHv4qQ~x;^>vy zFZ*~`{o^!}&Hx*yV`@!cPgU|SAwj*$K+AJDRo%Tj`(jd$je$~ZSC2%k#a9SX*D`pE zR`Q4swRYH&=u5s^^3+jItc(<@Wqe)$y=iUR(sb_`z7($t{^@ntsvWAy@LW*L7-?ez z>e@en(N6<$Sm`Yj^WUK!(#6zdAN?AUWARd`V9!r~CVk?g_ier@#zymjiqVyiXUky? zO+p#O*WWXHGGojA!1>hUZo`9IAb5>@arE=iZ<)bgS=;k=V)y3#$f9@Nx$n{3UB43S z1-aDZ{E{?Qz>PI`Dc|Ax&NRDCUGDD{pZ1>VY3VhBggeFiq~+d(ztniyd&}q5=`EjW zJTBs@fLeg}E*7lf5tA!FbVjVht!3Ff&n(QUj3hLaoQL8LSi{+AKKk4il8pqJ1x(vW z10yrG^=;5j#YJ3;;cTnFm#QWT8%>9Kx|_h{H9xOj)}>M}|BfuVGrEvMu8{POeeW!A z9i-xH%UO9$Vw##wlX%`qlJ@>9aS&bigzjz1HO(evo#AeNds0h&Af)h|W_g0Tn2_fQZv(w{BHy&&YAvb0_FrNn{ z{gPLOq}okCxnP7?wX~%(u#Kfu3>YNU8wEhy6STKw%|(fb_*$x>D6%z!@cnyYhb*vh zLE!hR`eBf~un;*%#L^L1sW6UYrTcI@qBcoW!0!(GoD$hRE&jk~OSIgsr`y+9$NtT= z-d%w~G`RrT61ty(cbXvB=Z>Cm@+NweY*lP~S}Jt@Wl@>Y%Wy@6o~&9Mk2ZI>RQ-mc z_JcHDS9N5Z1I%u~N!$Fi4d&ee^mC4^yWqdxB7DRKAqQ7^)RL z9J7(s%J~s#DsmU|z$V4o?bMo-$f${$qE(-k?mz>+;d(({x>W3V1J@SFRH(GP{#Gp* z`?ai20CQWUgVIF9g?hQJw4Yv(7DV3u!76Ibc8@N9Myb7ysXmX?>Qkk*tXQ#CXw)Ln zTc+2w(|+|Og^JCjS9Uka7Lt?8(qV3hP#;X1P;1sodWd94Lt)}w>;{l?hV4>%ceS+K z>B4QoE_&&UyLp&Wahn19N+W%v4j3TizSqw=cI97P;e_=4g8#3hedu(cZ7L)HfGQ~f z0ONnNlK=k!>;KJUzohm56;M~8m1R+>=wWRR_uvRAH%$f6oJss{QYdCo+Y?$!+6EGm zF2TNU*gGfL6_u7qToI`y{yCWErvImaTB7~qa>ITX^V304Qev3V`NUhWv!CkhTh1m*{Vnz&cL%5UjXXSVl${3b!xfU>B2%Zs2=d^remi;Rig5o<4&wI) zaGSTU6y)IqMdpai-8-WuB5&3l@GtUu05w%n#QcC$&~U0-;M6f?QxLKH+w;}N$^2%v zfH_Z;Z~{>H-HG?F(fDn?Ez*)1sC%HBcf}A ziD9C%zKr5OyFy=wniU3UJSNI$6wSDCW;Ep*bu7h%G@$zR2=w!uzvFPEfa1^!O^#sS zLEO0PI$yh^;KIRg$CKFL0LW_-1MMzhq`73IZ!w5$%BsnLx)UY2O#UwUF(uNbAYzI@ zus>4ie9h)23v4qOq9HUDRg0c&W{{QjBg%2!jA1AlUxH{^v;Xh4BzyztUuC5s1b8F| z9mKl6O|)ncNmMXYejOTw^KX@!9yS%%IS%g_NJ1b0%oea*eKgq%eMsVDbsfD{@assym{2WuSipAn@hY2xnH9|>klltcy65+ zPwTTwBXJ=oV-~0YX2Dg%61vW*;1&xKMNe>JPLdHYA@`*$k;RaIOR|9?WmQR5FkqV0 z1QI|K7{~@rq^m;RU=KkaglHsCP+wI=e3ua;#(_93#wUMH1EGlfO>J#l@=VVfB2tyC zEBl2ERYb+4#laF0$yD%sWVq-Gr$JKyWr$7u`jxdr2nFXs2a%KnWF2*tRhZ(?!8Qj> z(h(Y9hB^pHA}XZKm%e~XwXXY^3P|F`!4_bFYmFa!pj3y)v4y#M4Oi)+Oxm2#fe%ws z{xwxBP$a21`Z0IN95&rJ+irk*BrR01m*?4fUJq`TBdCu)BAF!*pg7X@4<m>6m#GKTnNAxR@+jX2q?_amU$^ z#Z_E*28>1L>V`9Ci9K7oGi+)EGta87_A&r|J7n#Kdg^$r=~n#gX3c@;G`)uzCr=t` z=#IB86nMvF_C5cizx?lWW~q-hwm-`w&Pt2-nWc*n3Q=*82@Yhlk^B5+=1s8CgJtuJLCM`rJR{uaakY|DWU=amEUV)HUl zZ*v3kE^K1$ULr7(vn43oQw}45VS+4r`|f3W=Ho_7EearDHV}_w_=3}~@x6Y=W zD@kG{#vI>J16Ll3L+hzI!gJI&o{}lUBEP4q*gnl{UJ3pVLF@=+NEx zf(Sn74kJkQYti^iqK>>g-f*}OrV_HnQd70!xx0^0)ioB>l@heS4$wIFpULD<5KsOe zBw6eekC!UasP(`T(Uf5)oY}9?QE;|YRQi0+mtI%5GQU*TqK_-~dw9C7xb&)pMj^V~ z%)dr zHShnJq>tTm&-TC|k0%avi2A8~gFtj=%FP0;O!^G++snvR|HM7>?lhTkbQpE(InfLm zV(z_=3_{WLAq7wEGSv|wC2~fvqjme2Qo37UPS1OqCd)5r#{z(l`iVX*w;e`N040?k}0zV+o9+9eS?>NV~|r_+Uj^J23Amv&ooK_6)GT<27w5zE6JIU zCxCI}86ParjoF!@FpzF~Wl4+yMJ_5HfJr_0BXW+t+<(Y=fN~0m;7F(ymE?1m)jl@%1yB-WEAbsLxu7sV1!wcg%z^mB{X4l@b$!Mcq?N)^Fj(^26??9FrMTC zlD>0L7WdkZ)L!ig#=#SZ;ETUVQS$Yr;J7@A=74KMgn$+=Y5dL1<6@3{Q-PznvbrnM z==58a;5Ui;c|9lxTUA1%M#m#*ap~GJ91aMI1)!8w6F4pbzTkvDzIQ}2x63eCIYr?kb@`EAff{C2R%l=TWGyyn z*nLSUY~t5TeH?f`Du~<<3jQ!e0Y(nwnUzJi+yOTHV;1bBtLTr ztPQ8~9?ISc^%F$;lRIsruM!SF`ze_y1Mc)j9NtLr{sEpj!>0tU@k-6RlQyT4Fw6Je zY2p6QaMzOUMPBAO%raDLc&QWWqlmC+e7I9~k$jmYR}cBqI^xcM%CM5;b1kW8;4A$G zx{tEs1r;$`hN$DjCjiS~Lg6aLJzE%tK)KfPp23dQtY6s#QP~#VD{ZggIs3Bp2-(P2 z!Q~5tZOOOa3j^cKyIo~)8c$C=5F8~tZbbexdN@+$2sRZfyDtN#APK~8T?%2L4gvAsQYme!Q;gp@WTomKJ7k-bk-?5sRe1@~3= zv*k;rc&r=NvOs5K;Bw7m>O@iTmC;83T!k+O<$L>$#x;i`#WtU_bEs&&*7BRi{ZZ(Z zzUW9F#NQ z-d&7q^DN1XED8mNhtz})V*ht|q}|Rv;t>o0fc+O>W&6J${VeV6{#VBPe@=Fr)pzU* zC6Ih$lYa7(uq_i1gAy;V22Gp_5wOX#a1KDTHoI)G*V(Z16oH>#)!ue)E{;M| zB_aZ9-aF1K&nt{>&tAj%=6b*3Q-S6U`ZE%f8xi<>G*Yz5IA!(NwY)Eo$rn&Q)g07PEx@GMhYf~)PM#^8WMt=jPe~x=-W1L$1dY|@rFey z4@8Su!c7rXK}$Ibg;bah-=~PC(>CXn#+n!`uv4v(lzt*B78%OJ_f2umZDEzn4s}W# zHSfcnDA^o?Go7ZSJ`Vt-km~<7qJ|Jf85OH+l%go>>A4*1rrFcbNYH362q6k~QAxe3 zGNo13tqIvIDha2F^(RLlfnXyi%;M6c?&L@$Lc}-uAQvuXv`rxu+#q%(fXwNw7||2bdmE`!~&Ob>v>LO zLWDjRFi}%M>r8UMDy{#BB0zR?$FPl3Gx*Zo)nbxE4+VcU^Z zJnEk7?>XPvwAT0-kD47OG)Csy8BZu>r8eV$9)zk1YMz zK};FEl<-;o%;0f({28%(w6@)Z6~T0NzHQ+IY39c~>#l8b<^0TKf-W0F^I%SLzVf;N zWxqd{;j;|zR-I2ei1h~pG7=e=I4I}Zw(AAuD1%d4aX+Zvu#Q}9 z(A{Aa`q{gr+=t!q`tod6t+GbQ)5-e-#?Zx^bazpkUvt$BuPMEt022CQqZ@_I`QJEZ zBCDsfbItOA1nQ3Ed^HL9^evB0tn42``h4h*Ew=o@`~seCth8K7PJe z(Ww#^kr=R8Zk5X9kf7czt8|L}^uwN}G-|U7izwUlB8S2YcKla}llmQ$p0z&ywZcvZ z783^_7vI1DvvxUEw^i{#$7iNu*FLD{syeBgH%e)H9O+j-+dmnN7N#&r>c|NHhh_=l zyg1*Y2+cbD64hmvJ0WQpz9kg>lbx|~fNqog^L?e6C{LKz2eODpbF4x$=yq(W7_f6y zNI%BTa`k=1)ZK#QZ}GCD&5&~g`ii$j`q^HI(^M_Y8^x}N3XMW{;y@u~&uXzg`@)g6 zivZsuAl(B^>--C|j*Afte3d%sIF$62pik{o=860fH0uCJu;rBz3vh(&kzDi;fI7TM0NBN_0*Hce97`I zEfUA@P1`}QS*dsBqaZ7H7r8R&h1DA4K>et)t0oSe8?sO+YsI7X4A%y@3OH_BjeO&3 z;wft(J|YlZy^LzHQjehc$YL*|oAd7~t(xLhG?O0)kL`bR-1}RJded>IyI%RD`B~Lz z?>*&CI45PoIE(DCsjNXwxre1+IT z4Ncz7{Q&&eTm`yBfVk|JqYy3y0HFA99#;oLV{1cmQ#uQ0TN_msNC4mm16y8dLt9=| zcMyO_KtP}+AM4nIF_+U9uy=k0C_A+YRcXBstTv1pnOSal_h_GJb1qG6K?DUO$$~H< zQ?w;4&N08<6HS-MB9`t>$lcC9IX7t&Ub*a-%AA`HpQrohS~>sC&lAN+<2! zw5mPZ6}i`H-3vB)vY)INF{d1SI&)&(8@x+_+8rxmLcTJoXj)l{pn~me$ zb-0m$QhnB^l1)5qFsDze$EjFc0Z2Wev9G%KC~Y@P9~M5@?fw|6tV7u&1g=i@&;{3>>j<2QSKJOsde zxV|&j@*WokOVzzBgCFN@TgR19JJ7uk9gS`AQ6FEYTtF(_)9~LKN4N$!zB#`KOZMH} zKg3-CKj*E|ePpkk0^B3`9u_Z|srG!trw>$(7@$M<*uHP!DCwp%mG?(J**@Fq0$A1H zP@2FcELV83h1R!-?h@)v7afC*NB7QLw9jy{{p_p>r}qb6xhaXMxAxB$!?o_Px9dn? zE0Vqh31gM#JTv!P!d9N|!G7cT4bI^beny}_dLdf$8B@;67MX|6fETpT8P`8fqac_SeCN zwA$*N1{Bh7A9hFg@2knl;_E+RKMJlXvOj${=C!YHnttrPP@Ql1`cw?F6Nvix!#$CH z-@0_IO3VJz#j#9#h`kQ&GpQmZyi&;D*pO39`x5%#uRI$kPn?tRVHG;e2QE*>Yiz9J zaa9{fx@sH*bINO1aU|Dq2yy0Un(=OLJk*_4kFD(DH*BL&cdkcF`y98G5>&-x$+DW7 z2ja*t;eIH#ko@Fr3G2-d8<`8!fKkBn7Za=ud2AoRvueu-sbTfIoh}#Q^s;~?UMp7_ z1BDdMxzaoF8i(fg_P2WtzBYXAMk)Pm&A875RrvbViqB|GQlmrz=AEB&{dPJ&UJD&A zjn$LA?MCr{t)9-PQsf9zZ#`8~7*TyeB%k#np*dTFF*dvt{H2)?}W=XXIEx;M{TButA8mb0drW$>KGwGcFx@ES2Y-?o$!rg9znL@}! z%Y^jZoYW1wz>R{_zh6It`3E4Q)=b8~BeK02w@*RVz5WeA{C6qvq;WItOw-u_K$f$$ z6+orPO76b*96FjXy!#J(l^8~QLBs4Pn)4Qc2&3=q?cQLv$r9QZ7d-=<;h$xaEb)SA z9zpU2Ob;hRLDO~ZXoJfzGu~fOD5Y2G0RoJ2S91U8n|myPK`-=kefAJ9578OzQ=%OD zvpE~xqitZ{F8eLxH5?z8<9HP9!L>7QJcsY-&gZw0e#mAxS9Sl}JI$p&yWNv^P9(eu z30NZZm=F~&h=*A|%pggl{7)_F?g=|c zB(Rla^CMr&*USy64b*^#X1Uv4wy9s=r*7jNZ(C9tV~aBeCjgw>8|ppe_*G&PH-JzB z`fbGAYv8T4mhA#~9U;;g6-ujmsH#V5h%#gPeZc^7f!zGbIT}!o)rzdk2-xY!W+dxe zfb|>6dM7|5cpeIbI-OPUqjU~b0x$f7hyJ5HO4u!@noYAZk>jdw<8uv=KHymxU|!-1 zmPe-s-zdPpKR^gJ3KnPC)8TJ*f;j|Kt{b)SxwTpgl9l<0E+4b96&6+64hM1;sd)&w z@QAJi?t>*4(v^57b2zI4`!fc=*%^$cWY}&KL|!2a2jd}1+UT1O8b)z6T--+Zv831hMWRD9+bbn0=kq ziVFxW4{^^EtFx>|jjtdtO?ImJ4(_=5mx{Iq-S~aZk63%67J1>V#x+oLO5j5O4356S znplrztL7>)?Ijw3LsYViT^vbVDwM(B4%$DstTdhUF9;D21uB~Es;1_}k{n=26IRK% z3UpFwIQ9+KcibkEFM_q(j7sh@(6XDc1B9!84p2jz>lGQ?t;MJkO(EzX0d_o>WoDR0 z2j&@Gi3j9*aVH=l1M<~asLTm-{aG?IWY|5!P-$Co|e9 zgzpPq#_Vija3>6-gj~r>TR{3Xej#O^LAYmVciIG-YvNkbF}wIyBsiW+sU6^Y4EHLM zro63PDkzigem!C}=mUVIYt0|KKCS%ufxMKDG*C$CTdo7QlcEVDPHU zJu%ermKw3;V<>I{4IIy;exsJcDdFJyAgXhx%hPd1@R_;NjNi>DHS1Blp%n)|8ULXu@-$QvHrQe@rGxxl_bpkrBL%~;ePqtYT&3;5t+bk8gyuAiKGJjSiOoyApit`h|1~oSd z58*Ip5T?R%faJgghY0>S*l99z0Hlg`*SX772;rkcf6z<8Vmca-h-2SaG3}st>WaCi zYdz8MVWGTYaKi9$(jHvupY#iBzxHqcAxSBFM1<=QlH z)kZ$G)fctaPzo4A+4T{8>s-5XIS)(>C{r)CH^1<;rw z%V02X7IxM66v9CTpKjD?!ze52&eFJzgHx;S0`2Ni#b{fP*;A2NbWHQ14lW}I8hm}3 zRdmj+0#)HmZBCYu@n;%|J?;ALC2~pQ2|ZpDWc79&wYfiX7!tyIWBTsXD~koWk1Qw1!w=A&Bx5Cembwax!wr|#F$E0wmp*?nZiU*yjIgOQcuBBU--7%i4pk44Ta#fjW^+9=gDylgW$}pa*9PBq z8&-;Fk)siMN|TF9DFg?4=&m;8l1ew-R1=J%2&VWog@OxU_LNk##*^{_7 zdCn?AG5s;IF9C1lv#-_;uz_2=P@5{uj5>p-SL=cF<%MDIJLNog3uFGM&T&PXV^s@} zd=fY#8V6*u(^8O@ll_QTUNLHG0-d+PrzV&wbmRM5c-jEURvghw@aVFL z5F^PL=zRnLwP(=Ee(!!hvz|oq62Yz11|cgG9XtSWFT{!T_b?65%7#G)_Hn3#Y5ENg zzS9*~oPQTX7tk253hyr=Jr9;oQkD%?zDi(OGJ_&Bg+u5OAdl&hO!E^nZa}ik&s0u9v!zAf|96_8XZm-^_RlE1Y7m23{ znRK*JV=ba6TvsCqaSvxpo$=dl%-%P?{?BMFny9*rS+6i&KeTBpDN;CilSm!ZHjLxb zY2kYj$H=#O(^n^MqJb;XJ^^?ux;+#z17}%f*EU`Bcf)GObx;-iE2*7*Al1)s>NT&x z#L>rXgTs|XeE8%iO${5wa|@@P+lr3xYiDab79Ua{h9b@Nb;Gqc#Y&L?xNhLeRGj`N zDg1oKEN@W z)*cHfuf(`|294TLqYyGr`hdp1y_xg}oOP$%Rt)^9?sZU04BdHpxw`o{RfPm&1b4l5 z+BM{$dMc>D%t#DjXS2->;F_2mab#|NWM{81^!Me7%6;tcc1!Rk9B=3B1cqAG9)2dsU#YWzMX7T)b03EO8EJKT9d znZ3Nt5=KUJK}yC|>0?fs_GU z4d53HO5k!D&6VCDr3ZKboS-~8>9cMk-kT}5*SCpeK04##8FOVEDsJD12nBVGGEK-A zPRP4=mqbt}3p{;G-zX;{6U;1DirE~I>^@Gv7Xip@y-4{LBf$*VF|y6L1K}=L-QLY6 zVfSD++r(J23QE|1DX$dv%t;+r*RW7il|@05L{cC{b(J#$bwKr|hzC{G8eQYYu?{>x zWrm!3aV}&?VGOYMN~5Xm8awYFb|;4#4JS6UL6kYxfN7G*_ZGxA?GabxTs4O;p<@PI zLX%lP<~?nqRAy2oI7$8cvNO>(zK_MLu0mYnHS$Q^#2yk89Q8hSE1TgPD=O#&ry+Z| z+1s<#sTtLOiJ&xAUGtcr+Wp+WOhGm}ZJYiJgK4f5QQ!m#KVp!NKK?v8k#gz)r*_ERs0$^yq|LgQ}`RNw~^$k4k5A)BWS-!XPv2MYB@W`08Eq z&WAX`_cIg>(2)L17qItvO7|WpGOXlXU;B5fg91Z)Jk{_JWOXF|;q3+l1HXbu4PI8h z6=MCwBrhZ>+}*1IXfp&9ypZHj@HE3H#5XQa=$Q1MT`j;P#mB2X$x$|BaD&zWwiYME z`TOdug!v;nLBL$-nDb9btxGVi(L0_o!8BfUCc=Kf32<3`sGCJuwqTL~L?=o!0P?0^ z(S(wj?7ZNS=?23-wV%Q-Xt~&oI|2NubAtU_Y$pRLtn zw^{;x$y@5bPIwy91mGvws-@75R@Fu*NkhRvlFmc#2n=~rF}e(+?G}O(lMj!;wG1{? z#6%=U6;$19kM6v&{rV?JFEa_jEnWY!{Y7y_<yoAuKs@g68Kibf`W> z5P#q?J^8m<5*h;LV>NKz>a7{z`ugHGwH*6k7OWUym>q(MK#$1|V`9)=|5@3cR+n-R z7qdyd(^?SVO7cAci8;!T!d*;4yMBhyYz^Hx7&;}-h!jxVoul;!x$qQ zg8-nY8lDGUHZ$bzG@HXILhJ^|uOErB0UAlzZwfY1Y5XdtG*uz-r~X_@Xr78(8ibes z@OAb+_=ZGto`HK@Sp=CC9K}tHPxT~f#$t}w(3UWdm)Y;m(Kf{pdD@Ghh%=9DQsvXN zeWb`&TCy-PJ-d%k#S~+5SkoAEp>PoQHA%6a-f@1~2T0;ho7=+Ac}FL@yZB3 z^5gI60>?_vjbjR7R|M^)?+`6QkYKVzA}yvXM6+HIZmIczlpEi5$fAPLf)InYfEd$c zg&|9oAca+C4b+45g|%5!de4rpfud3SLlZIoUB;+^K@VjldQ{-pVphvWbj#ia&fBU# z5`h*c{S$4KyiXv^&5|@q%&p{(44dGG+uqENSAzm3TaT;gBOaTKHcybf$S>U2sroVj2H?>Mqq9*jh@Q{bNuSICm@ zk@&E>i2JM*!AroW)I^CjtjH}y>aSyI5L_p&^2l863+8(jy4^@=uT&1MLt{=z9equzDx_~N4BsKuEDi`%zf zHNg!7P!M*t8XAetq->ndx+1E(>U|Dt#GKyM$XMio!ONfkU}s7MjxXu4zkT-*rG4`@ zU3M-YI8Ud@rp?78&7%%gIJpa0pcQZ?YhcUR*;BoGIFbw^MP?cWDjtiNz8exvFhfieKRsyS>zUXP4Zlp)ay==>{N@HFX*EH zj0&kO0vmlxmPv>{vKY;kvk}e*WTj__wU-fq-Id6r;c!#1Jdy~an+8%rFOdGF24V7b z1(dxu-MpBX?1Je$!AEJBfr3mRmR{sXq1Vc4x)mTPW7e|~q#M&iL(;yR%y0hT)Oiv7 z6cvRC`o@L+%^mIkwrji4#{`tjqIi_UsA@xNnnacTP$!A>q)37=8b)&^@8;;cEI9AnE{gv>3qqq8Nes$RkTs3evv281envdkIZfWD{vm!9`<1aFdJ>cftTe)*@&X`cZh!W-A}Dj-trB!wSg{ zpTZN^Kq>|s;@x5kM-|Yiu)xp|QTDS?tvV_Z|3()wqH4?c*)4oZ zqZKhqRZI7oQM2Mv5LNDLNxWeMJ&3Oi6WIWt^q=%bbG8v28D&<7u6a@beSo(nfQc-< zfjUacFb**J9Ai6qk)PSqxzr-+pg*pJ>#oQvQwo7YktJl{HGt{6xK!#i_$}p{;Zk-X zngb%Z6>4Uou&11R-=_w$a5RK3)PMv31P*Be-|PlXg+K&h6=5zg{Z=&BSo1 z;)Y`T&)uyn+!%pno9PRxDTzzHplssqfb{Tzzq);9xJ@DJSi^Nv(@X4tA0Zx@gpokbM1}9<406iv z0*-Lm@dfD-SUhuF!@@&3t-1ok8gWM8d+;-cse}U!XW6(nOB(UH6og{(;4z9m;Qaj*>BbNYQZ|nBb?2Y3e zm^gnLa+>k|eUMyUR1Q*F8X>lo6d0PM(G=IR5tFnqD;%eMhG0oYxTN~$hzzc+nW6Ql zeN?k}8M>SoVP}arZN680?kjz_iO&)etgk?2HV${jhnIE6u!h*8wsoiRr%CEcfYUi< z!DKrMi<)XFXNxOAIfJ|#N<^s(&0=T>DuqOhzOJa($O*oB68Pe_$xzlJ>&L12$$Q*s z>lxlJg&DqUv{bPaVb4JSYH-*GLU#1(biRvh?UZa{IxTr40j+{D5F;)F5{(+bPW?Q5u+f3=)`p*t;ea!}pHTN-ImVC?`dO zr_5}sdx7Bjvl)QJhSZd_6c`}Vxa<-H3Y3LM&6bwMuCdkpWGBI+0jEmNS{5B5h4}>K z{nmG|{w{70Id+&5Jit2k; z(>H>=ej@4>l8&_66&^!OXJRB9I5BZ(z_X%S&y_V3I9KMd_z9{aocrBd3HIJ_>0>Q% z4aM{670FFc$Pf!{XnYxt^0Cbq<{c)RgqgRGpc&v|P%=e%9FZr*6-%%F2Ev!cE0(o<+)EkDiQ+3U@6P%IB!I@U)l9AN040a^ZE)*#P?!dB*8ok~9jC}`56`d} zG%48paJV8zAsT#4fJfIQ(+3LUOw)?ut*9)o%BG`)z9|hu2VLG)=8Y=q6X4Nqm6G3{ zL%HSNnvRlf4HJBAju#w~s8RWrrz7^~7A`P|Px2fn7A4^78n1$_N6tZ4oRF0-TKsZ2 zbaUszHM!|0F9#G$%I@ROXdX1y zWWLVV8~zB3P0QR)B{LZ<97Nz?qdbU|8-w%{t{QeepCII8(Q@g4! zL+1`&a0Is{H{2NPp{TF94VF|T-6I7jid70g?h4F+#q{a=QKq<5mPiLvg@{i_xqFPZ zgGqu;gW-e?u8ZWHo&_&M#7)?L6vib>o94`)6Fv#J9 zv=6$PsRd8sZSz}jz>;O~X0j5qlZfP|>7*DqnYr~gL$bacus*E9qw2l=g|v8&of|r` zRkeaXgAN%$Z-^F~45iehg>c_^%GWQX7KyG#Rt<)MJ2;(>3ALOYQV^0vVaga7!MHQ9 zUAf$7Tpq*0^K(=^U{Q!5ej4T%Ixn> zebDBkC<7nh0dKNT+)(;s;;^a4xe9`l>c8-_CE}o~Fra3cr9nz?8G-EDk^|&p8eTyl z>6eJ{NqRn^1$VOy-$Kh@(X%aLvuGe0R3rmdZ6OmUnM5|y<#{sYKoQ8tiVo~P+a9%xIa@jd@Jvsj}rBnSN0@4 zBFGc%LsYUA51?db$AK~QAAh=txG*3 zDYW|NFonnE`tRT^&5neFl-_|g1iTz@YK3(Me8$3kV?xt7D+Q0Nox12q;)QIg=Pg1U_X0b9P;1KGm0+jqcNe|O8imj2nd zcK8+9R7jh` zM1%i%GV`z>dRUgsS6=CgSRKy{0)D~mTDCEWkxmfTIWSmyhUm-~LoWtW$Djfj+AK|n zGG{_+vH0(aOS1D(HP$Rix;tC#urpQ$gUMMP-ln?py&g2;wn7{=Q3L6=fY}W<#VyG`4b089)9P&ZD zlcY%ft29Onv3msLw33U8JY(gd`7+7kiWOFaRNado;|7e-m1dn$$Ij=QweB(4%A6;J zkpvpiIG8dy4>b8{?q8h`Z@hM4799}cM%(^}3N7Y6zxf%86=ZG~Ri+CYot zCrlg59A`gRWi@k!&={fV1ux40#{0~RrDf8ZBrqQS`AfU+u)5MmM%KtQn+X9bUW?+q z6+C~!S@X9eXDHcDrVWvxN7<&4^ zqgC*&H%9w;|5RX^%J_XL*M$fG!OB z?r$lG3o~Q3iRzPk`_mVI`Cen`ONGf3TJ;LSt>>+I_j8gs~3BY5wfU?R{a?v#1G|EOmsoYy4>=Iqt{f|_uCy#1qr(a zKW16-UvDYt{e2#$w>ocj==NBh#~M#h;D?T7#tO(-# zW-_lb;2pwyWDsZyuw889r4>NQIuWmUbBt9jhL#oN1)0u^9!>ef5qeRQS(#kyOtCnqIuT5YLMgNf(GwX&EDE zc>KRQF4VdkrU;NLE#nh?KWQH_Yj#6)%^HQ7hGz_d)$}FWe2!&C2}U~~z#qq}oows# zQs+Xm)hU%BDak+QhEZU{Rn9OKjMS=ePT!9WQBWh?Qjff5my^T_j3h z)Un!1nHGs=mz{-yH;TxrHDeaav$(Y%7r|_q=M2muTNUY>xbW!c=_U5{yZCNyzY~8( zKehN|Cp`QVGbkr_RZaiAzihl^yh^Z2!@F!*EuHl8@fb3x)EIm^X{!dj`!R zB*xN=rdDT{DX#{h%|@$#zptum?@n!vJ475lsfY6eVssb#dNS_bsw=%k4*X%`@xmb{ zI_)PpgsQ*}%S9#LZ#7bohFzI31AyTK%FPNVgGB|yOYmwK0sUtIFXfD@F=f>5!Kg8H zIQS^7hKlff7ILffAtVPPlK1$`d6##l%Ifl@y4G}O^+zx(+c1@iwJ~<<^vki4a%EL) zt+ZAu_PY+%bcot1e7%Ix-fBLVPP+GRbQoWv1<6hG$2U2F+c=SR5A*J zH?Mw~6%{Dy;hZ21E?7jcUokee6kQKj@C0#(|6kd@Do!{(X>)WKC{JR9WUaeP9;d;Q zD}>+a))L2ZcJAVt$&&YL`{y+d=b9+R4*KO8GbVdI7Cj3M6hm$me@3lI-}3+xMhxXM z7DZ8F4S^$)GzSE3h@1z_v^K-aKv(CIQG`-bd%obm0ox6#c4N%g1bU7E**cYh{bCIF zsA}>d86iOt^AC&%K}QG*9rkuRsz*QBTv@aFot+eB91c)6*+!isTCC%Wi07?0+eIGI zc46^0k1_ns{v$xmc67|j&LI;c)IeCOScGiFv_hBIw8CJmN5|_WyrS(!o5~@ikr5D6 z3+gcNi5$D8hUAxR=4fN!5n|?bJght&>?h7ug6fz%$Lqm)=Bj!;Bv1Qum4d;lm64 zAG1QtE_$+?-(ksS(oC&i)J%OP{N}G(E$;3 z<0nfOXHbn18`a;GU2qV@fK{VC>U0fTAUVb3Tl z2)G3uG3>~1P6Q6nBbAti>we8!D)QXOHc= z6*II)y;b(sh)*^pGo>;bu}L}#t% z(Ww)amwS`)7*a;KjnZlUdfsCB58;`!T&^o_%6YksP=a(W>q<~Z!hW8NPiz@g|9zla z%|NuLN4wF+KZkv1B;XYsmdDYyW0d&^h7Bvk+o{TsUw?&DRBV|_cQ*{g&af{F^M;ojc=(NkDAmZgU`8l5_a?4 zQQajnF-pdy*!{f$xMcc89ypMvRN8O-P$7iUaIIs0)dnNuc~3k!tsc9+i|J)}39CCl}rr7~h^< z8=vb5-P>*yYa_W}OVdimb^j79#&2e2PNK{I=3UtL&f9Q!b>D>1Yh*txW1F+|b+TxV zJc^UH8&F}|eBU$*ihXOyhKht7iSN`sD{o%x3Y0OoRGysx>i z{`;|N-NmAwY+$!bu%({;peWpBR*b>ZN{L0zi|q!Y4p1J}s=!00QNp6YQC{Llo2kG( zK*n+ztD|OzbORI^ki*e1Tas5|m~1u)D`x zbz`DX0snk(F{6fWzC{ZW7EPKnn;gC8gd-TtKI()Nk$4*Ko&N^8V|yDDOYF0y*jK;h?b>K-Ssw};w~P><(-jW$66Td%$7!hQ8Cx=E}a|7 zk6D&h4k_S=|JAH{i1(YAL!Ja@oaSJIYLJvVk3WNE@Csux)m9mj7)YW>EPUH`qZebsEpC|U8gQ>6}q}kl`gJ8dFo$M%V7I=kU|6)7Pum} zN=r>w&#@BL65^ui`KBI|$Sk%rmc=WXQ#7mY1An=tdkbojRCsnPwdeq2cCa)^^|*JF zff}UkZgkf2jEd{rxaPf7JG2=HzxnLOBsJT2wXk+uh=8%hkG8I1*{$is`@UTMwDG~O zwe?^-SwTUNbn``?{uL*#I4}AGqQy>WVr2W%#{D2$p&h0QVk?WQCz-br9mv1=bLRW} zv~Nub_I+Ydu^4{|`W|_XE1s8!{nC^6{#|}CnY^Y_6sOvl!>epZb$P$z*Y$Jz)hqt& zp0?F(xl!sn;_Msj{|Yp^D&NW&rouBR6!T%+b32-OxO3tQitocmL(H!G{xoy-VE^Fd z4dScJX*!1I8IO%o@=>;uiXhR~aHVgq%rg+j68}70WJN-Y9tCG9X+OpXv`^olPyY>{ z<=E5eL-K{EpxEkdaB2FgD1PjwzXys`JzU)O;1lu2{RmB!_?vbC{GSyND1iSQyCFEV z|NZ8FTnPSuRzRGbo$3FNs~`XCR*7s|T)!b40D!3o007N@;gP}?hIV$QHvjL7npfKY zH>iUeg`+)1bfeoR{HiU1l4Rd^n_%=SZ)|*iKElnV8kaY0bTKJu(Of)u zjWFFzbmzrQzzc z0d0|8@2cbqK6QMmcr7q>jivJ`g%_X-8boAwSPhIt)-pl(NPr|LEdx}3wc6;LS`u&q zkA*74mrJvh_ukcEaf&A-I7EuAclvt4gE5pndWLu-x3{sUEPqSXEM>Sh6UN~6c*ZOG zsayi)j|<(7s8hNQvp_GsRRX^hr?xwC^=m`_+}Na*aL*vAQkM1h@wtpLER7?c5$r7j z0OPAe19JUNl2MqK%zuhltwL>PMCHj{qNF!m>b;<(xQR8tM@$6V9tr-7OEML;HHZom zBu{Dr=86d3azp(|L;Qn+33&y;ak_iZJ0u}x=J<&1nkX0N5^B^HEZ($ES)ZRhF?Je0 zCBT@d9jS{SNjFd}&IkjnAlB!HK&oOI0C-jpE*u`%5#%PQIPnJuLtuE&kZ8n^00kJx z*hi;h!cUz;(>bo%$BOxqNx?4OBcUiwvyw?J_lfZ}(AX8vu)`!A&Out)j7!r#x;^>5 zJ6)GPwP__(?bp8Glo&HzyKm7wk=;@Qo0PsteKBoNgY1zzT~-^Dz(>^k&Bd@6=KmQ4 zLq`1iLObuDG3~k-+;WzU!QNP35XS*=Is=Y8IX8{RNH%z9WFn*YGl_7`b`+u*dqz(#A3p`jo2O!&D8aL8@-xB48HOZVH2b(4Rep_OQ0J z1j#Y;Bp%9%10Rj;3z8JW2)HQX|5u*INDqF**v^vy8)lC<=EbTbJsK>PG3S?@H&iFX zlr2}?4!A9ULe{?2|I>e@fer@%OKe;NPaDnRjRj=7`>f%@>RbvB`wA3&hHzo<-4d1z zSX1%i4H!5lA@-Y>LA^z?MP-abGK)oG+LnI7jsZJoLPsY%S%;P?rzcxRjNC8l`@N2F zevFAJAMp1K67r)G&*Qrlix< z@U2#9!4E5%+M6Ey0eBE+1U)7mkw0t^q|72mrOBc@K$UVfFHm|3M=&@t!&H)WHd6ud z&O*u6PRhIUmlia5GeKfdHTu4jz!baUYGHP)M>N#c!#n0V5w38PeQ&>i{#lT3+`(ga zViNRmj~2s=VZRSqakl~LFl4`~^G?+Jh|16D94eQCnq_uP(lQrgQu?}}^942rh^0=x!5OXs{yg)C1xbcT2jASaI6@R!8hrTWbV0`9ZjY92g zqPGPYw0^?@b-Y8IR2oGO|GjYB;dZj#Nk@Hy`CX&y{%YGm^dTK^z~uRMu-?Q3*7F4L zb_4!%{;;FUazTwU#u0Lqbf?w#RR6RjvZ-44i}c!=)kv;7jt9S!NiaPdc!v7N+2&nGl z-MxN4lblmX@E zrPbp`DMl|WlI&Akz%*LTlz)8pxk6z9kk;hq<6&&RR06%jdC-{?0#d#x{-6u?4?l9o z?a08}*k`Y5N7zo>?KR>;PE@rjvP_s@OH2^9%qN3azbltip4wb*__=I(@{vZpdC+eY zg*79C3CYR`I#Y{6Bs6LYN8W<)uUp;%Wq*P~wuZNF0T?>47UI5L?LsWi+3wbGpYQ6oJg?@`dFu-f)O|bn+tI98lR^h) zas(c$Ss@UaH-R z8_x`5jUjN@>PVyqUQr=e$nO{Aga!Kz4O*`zsD)qRG~wcWNP$3tBG+)Y}>)YF5rHz;eBti3@7~Y zN=eP74Zhe-9XWgJLRP+v-nja+1%XpWxdiQ}phzYT1;}~-Wem)VR1re}s?wxSO6e@a z*NprmVGoQg-wFY1GfUobF_K*GO9hs12cn|A+@+WZyHsppduU={%3ld`q$`tWE%6@h z%aZ~5VrS71y$SpA8gPNcEzqt9 zCB1Q+5-Td%3S@6nt2F0*Bsc5Cm+*y_?Z@_W(UB zC)z#X?z)?ed!dzWju#~cN-`~hH;Df3av1)eboR=jh`+uuc3%&8g)0Lk94h@JdpS_> z!w8CW0r=ZOBpdXi5fy{EtFfo?{PWdf6KGZ4^H(<+Z;z-OoSO+f1EU$% z@AUn<9y}^bJojb!OtT8cklZ%XAZ{>rSJK8q#@ulfgIcT0iVEF5e3U4%0KxqP)(_3{ zlTBXK2CpeOKoxF~Wm!vK!c6KCDilM{1mUEwf>q@5*;BQN*KV(AvMXcDN7g~D8tEE5 z=R(f$W#gY2{l=0mh+F>)@vg3ee~NY8@Q?(K93*)CsRV+qmA0Y$n&3c%EVq(~M-5m< z-yoNi|6Z|odBF>EAU=u33uu#6anaX1JSzsrv-7^T!uT5Dk21bHiN8>}lvs^zyCo%_ z)XP_|vVT4qZi-)09?y{MN~x7(Kd)qUqH+NSATb^-OzlQedB z2kay5uIU}BtxFTs@_p^I%tbgoG*<1;zrFR%YY$|%QZQ>#x44_~DOxf+ zut{33`N-;e?4SeMA0a7DQTj1crZ0|cm@=e2(eoYd=FK-m$zf-VE%Jljt7AIxMI)Dy zAwY-flxmAn(tC8_NbvOC+5MxOlo={sw@kA-7mHcg{5pl{_BCj ze{+LnF~_&VmwPuSPeigyZjw-H+I`jTL!Tv}EA2DT5x?moD!_NRn6cAWTj*1+!But& z?F&`AaIFhf^1W_)Wy)$b$E@>O6VsxYM((9W+Xn*T{&$c(;pO2NAp`&b5C#AM)qkOX zm9+g27s1NV&G7$fj%ofUE<&@$hSP=^lCQk#cR^Z8r377MeDbRxdKFAUd@yaK34lW6 z4p4xwqtT`nfWkk!qa9~6Fm^qW^=(*PGy$2Hv+G6XEm*7@zt4c}d3pD#JVr;k5u%86 zL>lG;`JYC5rymN5LW&4xegRAxgHqQ3nFcBDI-*9v@^{5m?4hN(K@pNyws3wKHK zZe=cBK0el1`RD`Y&D^})++P0DsW?Lp;tOdheoK9vmP1VWediJtKAcxh?w!bdkmR|r zpTRP)2efDzRNVG)9{)SgB0_kL?0P!hK2r|}aCxf`zr-G>@2;^k0*>OO#>PhCvum~d ztm1HUKe%wb1kBK46y^|Ojgg_mX)o4>K{K97P=BgoumDCXCNZ8;8cLDAr6h8^kNuS6 zi0}rw?pq(h2!)m6F;to4sn=}q37fQHY)WLrS!cE)6%uK_LD`;L=TAcbsKLXK(ODFd zyJ%_Bt2~ux4L`k3_XX_-h9VQab~F;^aCFVsHYa>KX`3HP&54pkhLAw{>_dV!XvADa z2!V)24Ryns4zMc0(}(bg(7%ha?glNQ@<(t+xu_0z`1cTM)EJaZ(t$)qj@Rm(LvIBj z)PY2ow*6lW)(iRfVZ%@bxbn#7^t{cT8a&(sp$OJ&9yr0G*f5N(jvYfMs6F_-Zmjer z|FksxZaL-jgr28JzCBAqT#E1J6Wjm%y`Yd4xhIFdmrLXF)unZ$ z{_FNnqdePc?DTZXfO!EqNgO|mE!a$v0hSjaQq_|#&5XZ7H`}og%E5v zb$QAq^FM6KCm+^Mo#ZBaSX*Wg&*Xi#_tY}*v#{syv0qkiU zzYIFO-*?8;Jirfs!qdl4y$-syaoFX@_*@GZ6i7!b=e^D3RU+>>d>OE}%f_#BL%gV7 zT=--AIsYi8v(8+#l9n}usi(ku@@!`>!P6Gr_9m&p$Jm=#qXZj+)zXM7cPkH0HjGT! zoXL$g@IcDnp!2K1az`7O1=|~B(S%onIBt{MnL{OYI$~|KB=*IO2Nzam7B3DV5vZOw z?foF(WcAkXvdzV|-~YkO!V+9yu+Oc!hn(2<`aK`U4*p5enOtpW1f9W*P;K#TDJ2cU#lrT4Dm#xwLo)G|0D>wA2tM_`u_Dx~0h$+?BTVQtRQl1`+Qs zeKWjiy#}SO`Ai(ytxT_3*@SJcpvTsBuV#XCjokze&!$XgJ`C_#!KY#ikiSVtwNI z@r#wzN=_C{9sLhhk}TqdVg;4nSnI>dJFKJ9I{fYc{vJP-&lrJB6Ir_5N#|PizzP1~ zJ|y*6F*7{-aB1^E(w@j8o4+uaQ4tE`h9xHLECsi}q;!W|zM;RoAB@-}yQ&9wk(A31 ziC^E5Ggcq#sI|euiULiS6Nh#-1lK8wLI{oymZv0u{dA!r+bz!9JpIW9i1E-QqjIEt zMf0w>Ck+>T+ENdKb=`y#nqlqsr#!ccsaZtzS{Lg~wVRml9h9mizvuRinUl8z{=ESI z16n`rDlP^1q87|sA6f-!VW09)@T}g8vf#&x&QzEIhU;CQuc5V|;xOBkRw0GtZJr(} z(wn8127zb!VS0Ri{EiV6T0h^HsAo{G#PAfG$sM6%yhtf^`81q)^~NfMp$U$Uwd2b&M_h3 zv>T<>Qp%qJhq?aM+R;&Nivh`sCcZg(@{7YUQwHZU61Vt5!sk210TwoP%5vX%c71G* zv4dHr)t8+%-sJ5bU&i3M*j3Vzo-tIB$z!wK67u`|X2FkT26@hpME}{dF;BdP?mKTK zD{w$4Q+!~oVq5p5pb1>puk$7H_p9vEnOpA`0m6Z97yvKvPk~kRZV-!0Td|s)zMa~b z_mJ6JzxNn$UFs{2lQ<;<@nR?g{c5b@mR3xgqtFgJ$k&f7XxWMOz1#xMnQaR?xFr6( zGj__$WDYaJH_R_T{$%E49Q!3a*(HYqgUS?M9rvU6vV5#+6l(P&cyO!Ra%4qlzai~< z{FF(mq$65{;AaSW1Iq3GdlTBINKIPjIV?M=Kf+rX)REF370J*CL9sq=6>QV0C%5~w zlCD=_1K&zvnowRyj3z&y zTFUGPdNNYq$1t$XORE*zK4Je?^*j=vcF`If03hQxf|~NbQhm6X+WvnPAGXd#Vo2Y) zm7igJs+C6e;|AQV_@78CMv!a8CX(P|Gk(xw#Fh|=wCsVRueGaH>cbT!=G=(R0co9%_U^6+hFFWm!o>pB)8rA2TTB_JqE^elIAG!p z^e6ot5ICGo{3R;l4u!cmxw*L&Z*D=eAgQ*5O9^O*$R)VcbPSDUPR~vB&X59Wv8~$VBn+-Z{w%op+W?!6rq5>fdW*T_rgYn=>7m zj-w?@*Y82|Bwv&d@xus666Jcd6l$12*Ca>#OEH=rz5VM(wjLb}9BOc+MS~7k7ma9} zfS{wkJXQSVDa!s2p5hDPz<=i{YF%(mrPcIXWsC5bHke*KHghv<6qGrFGtyJFy|R0P zRKZ3gX@<8+X{w3I!a4d#7>FuGcGG$$qu64(eAlfKxExptfAuePMwvzt+d#A%2bnu6 zx%MMCqnFA1tnG@gIq%#mSziUC4(l`BCD)-Uzn;Wk)##PCG3wYgLl^gMpF_78MvU!^ z95}NR{n|T!3lH7Yw{}~8US7@85sk`C9IbA!KFR-%Q#|1kq|X8QJBxC}8Kpwy!lPa0 zp;>3LW}pA6WPT4h{--rDl1eSuv7KW=5jf919{LhGLrijEw-xiPY+M4ZWH-MI&!5RO z*-YtkV!vUfY`YIdnun^Y8y5|$OpD2jjCnQsacpURv=o`@;+LgpfF1Cql3YD51h_yd z4r!U6s=tdtTkg%Vsi=W4msWACtj#CHT4^~+MM+lu!{Dw4OY)RN=V zqIyM-y?T%+se)q!Yqgn_spb7;DYD+^o25-=K6e*%!wKRZHT<1sS5oo3aPheKrF4kz z1s!w-pJecNzl2`L`@IcA-tK?gjAy{yy)d`)&-$=K)UiE{cV@-#x|a*)UvMYQIS281 zCiQ#pJ(98ecI1Y!WQ4LjUw0vB?KQ!B+ZC-VE>bKaZnb~!qSa;B)g$AAj3mwSkN_f6qaK8IN{4hgq;~gOrYUyOR@ww$6A|({eML|Jh*M zo_FEH4*`TNO`3bz{aS)QT@U%0d0J(K`x9Dl(ix;6y3PkHfVqj6RfIOR z84!kVFeO?Y`_o=a0c^MXZT%mZ;#|A4-R2)SJ?2uuj%q^6p!S>X3JU>chF&-!`s^^e zEt#@GfdKFt{~OrN-M|s$I#yiEc+cHuLrRw>;6H#_2>lOFJhv>h+axZ&{(Q%L?AE4}}pFvXS|l%k4rJj#yb&|QnZ)1(xu434ol8(h-KoQE#| ze@ddXsICb|8Cc*ly@+ev6C0803`M@}y~4V-*5O}Q?C0>QJtEYqW$H0HytLZWO*V#F zJq!yx(;+mHveRhj|77l~eFPwl8_??bF|g<#sOdwbZF|w7Kd<+IaLSCD5BnKOc6f7@?4xfZffO#Ca!(YIwEDk~T2Pi2Wm)K9RV*X5BYq|p*D zweq*ZS^Z-2c&QRFC#c0k##_Owl1F-vGgN8@-Y6~NcdnQTO;vJIa}jWjTr%Y&MtL~( z5vB383(ZO@iW?n8sU?osjyQ$;E7s2je|kuo7(s@yOHkh4Q1ne~s+y?7a(oV8-H%Eg z_V=>?H;gW$l_5|a=~(rknd`Uc5IcUs9gEq8zI;LdQ~Tvo$V$4n+%fz{|%XGJ0$cW+lILBAQpY;s?T8&; zv$c}Ietr6pyZZAQP^76Vd4q92ydML6!u*5}W!3@`qd9^U3HAPFSKSdw^}6m^Fxef) zS38kOsj?AwK&wHkR$D1gsTZ<4Fxi5Q{y4uaS+wxS7z8*=#&DZXC|*e5+)gU%h)UsudH@G>`cSVOJ?6jZ57m!Q-a;mFGo!~7pfzW-P{Hb{B4UlCGmdF76-;2(A&*)+#Zz`Q_1QpMM+ z1lGQ=-=@iiu;pYeE^&D;0brE}gpbai0`XvB;$btV4#Lr19Y?Rf9vX7}VZ@EMsAW#5 z9f5n3Z-UMpIZ^ie-y-|iMuGzhew7wQ0sw$tNejT<$(-Ks_f1%s(i_{`+S=PW)0xFgI??~feDHhHGSG3-nYftzAElOQOi@7YqmMx_)BmAwu*E5PknnNIt-dL$dY0@H~1BCA%55D@M zUPf@+Lp_m=ra0{)9NBgjZHpJcMD9S!~4lthI;Y{!jNeU-I7jA zUeci-F`coX9})eY)8YFCMe7Is=V-5nlb?iA7d|F8yM=VZ=`@N@q+UKDVZDV-V8I5z z4HjBySn6uXlB)V4TKZkkIp7-fi5{ihn(SVgJw^AF$O{2Fmy>MQeE z6)y?{x>s*~^V8&{KfL|-EIMdK=NZ4U_k7Ql7t^h{n!GP}fYjK(UjMHUzE%7a_#?%q z6!aiB1wRvb=Szu!1Y97(WQZ;4lSxn%Du!iTGwQ~hvR3msF95`Ik$hwNSGcgpunO*i<_jBfrnRz1SocWe{Wq!(tf9`ed{o88^x<2ENEDAPq80YrG{ zR3}uA_5Hwt#7G34^1ZoZNd(L>VP{=sL?T26;i?-led`7xpZUL ziP_&RgFPeLMv-x+dw@?=nTqio!xjs0+KA+*XG{?^3L}?Fp^g`)HG zHuG)=qgnO2CHU9IJFsT)8a5fKgVHnO09|AfSR&96x7}oaCFvuQ^bnZEgOnH8370)~aHl9y`SVR5lR`>804+Qr{B!OEu?Vr&;JQf~oJKymHj-20>B?5szDN&^c~ zm}BP4KzOEoT70uA;WdFqlwk_kINWSkQWEur8$$TM&?GksqxI(J#Le;;t`WJ|9wW*S z^UPf;O7?`Kc=7XEgh9?ab}8@;Raci>`-`ngTBh1vcBo4agfoMc2ss?MCS$qE|0FJ@ zBB|=e12Z5j83u%+zQKo%$`Q*m=)f>t!m{&#E5r-5lB5i0@oVHE zi#l}OX*g?#XW|ORddoxecdmjPrPvPI;Oh<%qe%py_taItnU9KE4(%6Smjcax3AP@G z)2vN*1LX$MYIbhHx?O==GgiUQ9)a^b1+kTN%<<2|y494im1)oWB^BI(P^!64Z*Zk! za+c#bZ%>bP^+_$|-jV+6fZO?#LjGcjGI^*XV<}~u3oqkY)Afe?T|~cfIqz&P9JINE z(F(5eJMprz{@-5Ul@AXvGFEZBI7R>$9@L$U3}w0`vj)UPT2>9b0UpU?-j5V8VE2so zit8$f_n=OF;GOj1Ypn)cJAKZBeAJGsH~#2g9#(4G`xg0FlLo0Z@4d>A_^?rHsY&?R zDp)>P-agj}$Sx&#wG%ylwF9-*YGi|$Bj>uN?0jGhZQz_jApVH zfdj_ni!OC%K`>2G=iLAnfl&>ZrwRXhk2%lbC{&cnhc#?!oVa`}Y;oqO`)KTuN06>$ zpzB6ztr36?K5WX-;0B)duLB-sVD-_i3vo9p=fcgl3%*B+Ke`ou>Dl7U?3;1ypFvZ+ zNW-Rg^`JAsZA6-D4vw7$^~g5LZd#iFO&hSxWLpeEYE7Gn++=#p-k;A!@v>{aQr^t7 z$DVBhl54(RHN{7X!X&20<-jzEXdDf`98Ds6;_-{ z_}^|kRw@_y8xNP*FkG1(WK!}nG5vrk5)Ab zBp16-q+O+g^;QVgMlo^!#vCO?_Ym8%#*x+uzVeff)`?xp|N% z(754;WKXJV%OtBzCWF&0U~Z^`IGnmKiiOe#~%8AW;L3dz`>csiVr)m;%89cq^B;N{;_~NH|Th>#nloJzSrq# z_4}fMKl-R$&UUrut11>)oPgr#RxUYzc(tonrU0M(F%tqi8u{wu?A_fXeoau_%Mm4U z{s_|z8ABp~kx;ZNdUbOM!&a3_AL@l1!yELOS`J8l3A4{Dr+f(u_Kkj;N07e#3UEhA z278Nh`VATNj+8*ih%ub-t{hL@8z5*$JVW@47TGtl{P++Qp)u6w$uS@>rTfopgd-Qo zKv(#hs4ubt>-&Ha;4ecfCo-YJm-7Qn7Hi~oXDto!H%0hoR=cFkL&t_jKD#W<`@1)^ z_*1u?CH0UgRlU+y#b`r4@cm4T`HB2TbHrUys}h!hz?13_jmiYSvY*afGHk(6&EGjQ zS?5KwrBcC(5Lwwv9a`urH5j4}_6=*|7V)&GMmA|A>+`=3+^uOa*@ZdxHxj{GG=vX* z5>~2iO9}IZ0ikH}P59>guMJ^?9=wbT5ck4?sJUDY@urY8XO8=g=4m;0>(Bt3ZED&qgD-JexCR;f3|bheq$)dr48L&?Y4HEIq) z#Wq>x_?!OXeSL(M0Y?Oa4%M_uzH<=psrm!m3wBj*zs@C+qsbPjNQ$pg29Kw)YYk(P-L)@5f z5DyIAg-Q*ISHCDt40_vENrJ_lPhzme04$c}z2!lpROl$fU}MI+sOB)AIjumn7*7kq z!0=M{d7~g9bQP)dScX!H>XD%NX!rfGz%`o4ZA~kUxC5#Ga{(>Lmrh+Cp+ZK<)(2i6 z^{$KNz(v!z-ePg#J;ZJf4kKP~qh z6he)qa~+!ZjBZ
dUE( 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 z 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 z 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
U)uf6{p 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 F{|BSJb_@Uj literal 0 HcmV?d00001 diff --git a/lib/readme-lib-module.html b/lib/readme-lib-module.html index 33ce54651..1911ac007 100644 --- a/lib/readme-lib-module.html +++ b/lib/readme-lib-module.html @@ -24,6 +24,11 @@ This module also contains some bootstrap libraries and test sources. +
  • commons: + Commons 1.0 from + http:jakarta.apache.org/commons. + This is used only by the testing module. +
  • eclipse2.0: The AspectJ compiler is a patch to the Eclipse 2.0 compiler. These are the non-compiler binaries. For the eclipse compiler diff --git a/testing-client/.classpath b/testing-client/.classpath new file mode 100644 index 000000000..116a952ab --- /dev/null +++ b/testing-client/.classpath @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/testing-client/.project b/testing-client/.project new file mode 100644 index 000000000..2f32f3368 --- /dev/null +++ b/testing-client/.project @@ -0,0 +1,19 @@ + + + testing-client + + + bridge + util + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/testing-client/src/.cvsignore b/testing-client/src/.cvsignore new file mode 100644 index 000000000..a3f0b1b77 --- /dev/null +++ b/testing-client/src/.cvsignore @@ -0,0 +1 @@ +*.lst diff --git a/testing-client/src/org/aspectj/testing/Tester.java b/testing-client/src/org/aspectj/testing/Tester.java new file mode 100644 index 000000000..66aae9443 --- /dev/null +++ b/testing-client/src/org/aspectj/testing/Tester.java @@ -0,0 +1,983 @@ +/* ******************************************************************* + * Copyright (c) 1999-2000 Xerox Corporation. + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +package org.aspectj.testing; // XXX move to its own client package + +import org.aspectj.bridge.AbortException; +import org.aspectj.bridge.IMessage; +import org.aspectj.bridge.IMessageHandler; +import org.aspectj.bridge.Message; +import org.aspectj.util.LangUtil; + +import java.util.*; +import java.io.*; + +/** + * Testing client interface for checking results and reporting + * to a delegate IMessageHandler. + * Harnesses providing this interface for test clients must + * set it up by calling + * {@link #setBASEDIR(File)} + * {@link #setMessageHandler(IMessageHandler)} and + * {@link #clear()} for each test, as appropriate. + * (That means that IMessageHandler must be loaded from a class + * loader common to the harness and Tester.) + * If clients submit a failing check, this registers the message + * and throws an AbortException holding the message; this + * AbortException will not have the correct stack trace; + * all the information should be encoded in the message. + * Find any original exception thrown in the message itself. + */ + // XXX consider creating exception for new API throwFailure(String m) +public class Tester { + /** delegate for reporting results */ + private static IMessageHandler messageHandler; + + /** base directory for calculating relative paths to event files */ + private static File BASEDIR; + + /** + * collection of notes submitted + */ + private static Set notes; + + /** List to hold events submitted. */ + private static List actualEvents = new ArrayList(); + + /** List to hold events we expect. */ + private static List expectedEvents = new ArrayList(); + + static { + setBASEDIR(new File(".")); + setMessageHandler(IMessageHandler.SYSTEM_ERR); + clear(); + } + + /** + * Set directory used for calculating relative paths + * (currently only to an events file) + * @param baseDir the File for an existing directory + */ + public static void setBASEDIR(File baseDir) { + if (null == baseDir) throw new IllegalArgumentException("null baseDir"); + if (!baseDir.isDirectory()) throw new IllegalArgumentException("not a directory: " + baseDir); + BASEDIR = baseDir; + } + + public static File getBASEDIR() { + return BASEDIR; + } + + + /** + * Set the message handler used for this Tester. + * When given a message of kind FAIL, this handler + * must complete abruptly or return false (i.e., not handled completely) + * so the Tester throws an AbortException. + * @see checkFailed(..). + */ + public static void setMessageHandler(IMessageHandler handler) { + if (null == handler) throw new IllegalArgumentException("null handler"); + if (messageHandler != handler) messageHandler = handler; + } + + + public static void clear() { + clearNotes(); + clearEvents(); + } + + /** XXX deprecated #clear() */ + public static void clearNotes() { + notes = new HashSet(); + } + + /** XXX deprecated #clear() */ + public static void clearEvents() { + actualEvents = new ArrayList(); + expectedEvents = new ArrayList(); + } + + + /** Add an actual event */ + public static void event(String s) { + actualEvents.add(s); + } + + /** + * Add a note to {@link #notes}. + * @param note Message to add. + * XXX deprecated event(String) + */ + public static void note(Object note) { + notes.add(note); + } + + /** + * Checks that note was added using {@link #note}, + * and fails using note.toString() is it wasn't found. + * + * @param note Message that should've been added using {@link #note}. + * XXX deprecated checkEvent(String) + */ + public static void check(Object note) { + check(note, "expected note \"" + note.toString() + "\""); + } + + /** + * Checks that note was added using {@link #note}, + * and fails using message is it wasn't found. + * + * @param note Message that should've been added using {@link #note}. + * @param message Message with which to fail if node + * wasn't added. + */ + public static void check(Object note, String message) { + check(notes.contains(note), message); + } + + /** + * Reports that t shouldn't have been thrown. + * using t as the message. + * + * @param t Thrown exception. + * @see #throwable(Throwable,String) + */ + public static void throwable(Throwable t) { + throwable(t, null); + } + + + /** + * Reports that t shouldn't have been thrown. + * using msg as the message. + * + * @param thrown Thrown exception. + * @param msg Message with which to report error. + */ + public static void throwable(Throwable thrown, String msg) { + handle(msg, thrown, true); + } + + /** + * Report the error message unconditionally. + * + * @param message Error to report. + */ + public static void checkFailed(String message) { + handle(message, null, true); + } + + /** + * Check that expectedNotes is equal to {@link #notes} + * , fail with msg and create a new instance of {@link #notes}. + * NOTE: expectedNotes is a String, so + * it must match with {@link java.util.HashSet#toString()}. + * + * @param expectedNotes String we expect + * {@link #notes} to match. + * @param msg Message with which to fail. + */ + public static void checkAndClear(String expectedNotes, String msg) { + checkEqual(notes, expectedNotes, msg); + clearNotes(); + } + + /** + * Reports an error using message if + * test == false. + * + * @param test Determines whether we call {@link #checkFailed}. + * @param message Message to pass {@link #checkFailed} if + * test == false. + */ + public static void check(boolean test, String message) { + if (!test) checkFailed(message); + } + + /** + * Checks that the values of value and + * expectedValue are equal. Both or either + * can be null. Calls {@link #checkFailed} with message + * if the arrays aren't equal. + * + * @param value One test set. + * @param expectedValue The other test set. + * @param message Message with which to fail. + */ + public static void checkEqual(Object[] value, + Object[] expectedValue, + String message) + { + if (value == null) { + if (expectedValue == null) return; + checkFailed(message+" null array found"); + return; + } + int n = value.length; + if (n != expectedValue.length) { + checkFailed(message+" expected array of length "+n + +" got "+expectedValue.length); + return; + } + for(int i=0; is == t. + * + * @param s a known value. + * @param t another known value. + */ + public static void checkNotEqual(boolean s, boolean t) { + checkNotEqual(s, t, s + " shouldn't equal " + t); + } + /** + * Fails with message msg if s == t. + * + * @param s a known value. + * @param t another known value. + * @param msg the failure message. + */ + public static void checkNotEqual(boolean s, boolean t, String msg) { + if (s == t) checkFailed(msg); + } + + /** + * Fails if s == t. + * + * @param s a known value. + * @param t another known value. + */ + public static void checkNotEqual(byte s, byte t) { + checkNotEqual(s, t, s + " shouldn't equal " + t); + } + /** + * Fails with message msg if s == t. + * + * @param s a known value. + * @param t another known value. + * @param msg the failure message. + */ + public static void checkNotEqual(byte s, byte t, String msg) { + if (s == t) checkFailed(msg); + } + + /** + * Fails if s == t. + * + * @param s a known value. + * @param t another known value. + */ + public static void checkNotEqual(char s, char t) { + checkNotEqual(s, t, s + " shouldn't equal " + t); + } + /** + * Fails with message msg if s == t. + * + * @param s a known value. + * @param t another known value. + * @param msg the failure message. + */ + public static void checkNotEqual(char s, char t, String msg) { + if (s == t) checkFailed(msg); + } + + /** + * Fails if s == t. + * + * @param s a known value. + * @param t another known value. + */ + public static void checkNotEqual(short s, short t) { + checkNotEqual(s, t, s + " shouldn't equal " + t); + } + /** + * Fails with message msg if s == t. + * + * @param s a known value. + * @param t another known value. + * @param msg the failure message. + */ + public static void checkNotEqual(short s, short t, String msg) { + if (s == t) checkFailed(msg); + } + + /** + * Fails if s == t. + * + * @param s a known value. + * @param t another known value. + */ + public static void checkNotEqual(int s, int t) { + checkNotEqual(s, t, s + " shouldn't equal " + t); + } + /** + * Fails with message msg if s == t. + * + * @param s a known value. + * @param t another known value. + * @param msg the failure message. + */ + public static void checkNotEqual(int s, int t, String msg) { + if (s == t) checkFailed(msg); + } + + /** + * Fails if s == t. + * + * @param s a known value. + * @param t another known value. + */ + public static void checkNotEqual(long s, long t) { + checkNotEqual(s, t, s + " shouldn't equal " + t); + } + /** + * Fails with message msg if s == t. + * + * @param s a known value. + * @param t another known value. + * @param msg the failure message. + */ + public static void checkNotEqual(long s, long t, String msg) { + if (s == t) checkFailed(msg); + } + + /** + * Fails if s == t. + * + * @param s a known value. + * @param t another known value. + */ + public static void checkNotEqual(float s, float t) { + checkNotEqual(s, t, s + " shouldn't equal " + t); + } + /** + * Fails with message msg if s == t. + * + * @param s a known value. + * @param t another known value. + * @param msg the failure message. + */ + public static void checkNotEqual(float s, float t, String msg) { + if (s == t) checkFailed(msg); + } + + /** + * Fails if s == t. + * + * @param s a known value. + * @param t another known value. + */ + public static void checkNotEqual(double s, double t) { + checkNotEqual(s, t, s + " shouldn't equal " + t); + } + /** + * Fails with message msg if s == t. + * + * @param s a known value. + * @param t another known value. + * @param msg the failure message. + */ + public static void checkNotEqual(double s, double t, String msg) { + if (s == t) checkFailed(msg); + } + + /** + * Fails if s == t. + * + * @param s a known value. + * @param t another known value. + */ + public static void checkNotEqual(Object s, Object t) { + checkNotEqual(s, t, s + " shouldn't equal " + t); + } + /** + * Fails with message msg if s == t + * or both s and t are null. + * + * @param s a known value. + * @param t another known value. + * @param msg the failure message. + */ + public static void checkNotEqual(Object s, Object t, String msg) { + if ((s != null && s.equals(t)) || + (t != null && t.equals(s)) || + (s == null && t == null)) { + checkFailed(msg); + } + } + + /** + * Compares value and expectedValue + * with failing message "compare". + * + * @param value Unkown value. + * @param expectedValue Expected value. + * @see #checkEqual(int,int,String) + */ + public static void checkEqual(int value, int expectedValue) { + checkEqual(value, expectedValue, "compare"); + } + + /** + * Fails if the passed in value is null. + * + * @param o the expected non-null thing. + * @param name the name of o. + */ + public static void checkNonNull(Object o, String name) { + if (o == null) checkFailed(name + " shouldn't be null"); + } + + /** + * Compared value and expectedValue + * and fails with message if they aren't equal. + * + * @param value Unkown value. + * @param expectedValue Expected value. + * @param msg Message with which to fail. + */ + public static void checkEqual(int value, int expectedValue, String message) { + if (value == expectedValue) return; + if (value < expectedValue) { + message = message+": "+value+" < "+expectedValue; + } else { + message = message+": "+value+" > "+expectedValue; + } + checkFailed(message); + } + + /** + * Compares value and expectedValue + * with failing message "compare". + * + * @param value Unkown value. + * @param expectedValue Expected value. + * @see #checkEqual(float,float,String) + */ + public static void checkEqual(float value, float expectedValue) { + checkEqual(value, expectedValue, "compare"); + } + + /** + * Compared value and expectedValue + * and fails with message if they aren't equal. + * + * @param value Unkown value. + * @param expectedValue Expected value. + * @param msg Message with which to fail. + */ + public static void checkEqual(float value, float expectedValue, String msg) { + if (Float.isNaN(value) && Float.isNaN(expectedValue)) return; + if (value == expectedValue) return; + if (value < expectedValue) { + msg = msg+": "+value+" < "+expectedValue; + } else { + msg = msg+": "+value+" > "+expectedValue; + } + checkFailed(msg); + } + + /** + * Compares value and expectedValue + * with failing message "compare". + * + * @param value Unkown value. + * @param expectedValue Expected value. + * @see #checkEqual(long,long,String) + */ + public static void checkEqual(long value, long expectedValue) { + checkEqual(value, expectedValue, "compare"); + } + + /** + * Compared value and expectedValue + * and fails with message if they aren't equal. + * + * @param value Unkown value. + * @param expectedValue Expected value. + * @param msg Message with which to fail. + */ + public static void checkEqual(long value, long expectedValue, String msg) { + if (value == expectedValue) return; + if (value < expectedValue) { + msg = msg+": "+value+" < "+expectedValue; + } else { + msg = msg+": "+value+" > "+expectedValue; + } + checkFailed(msg); + } + + /** + * Compares value and expectedValue + * with failing message "compare". + * + * @param value Unkown value. + * @param expectedValue Expected value. + * @see #checkEqual(double,double,String) + */ + public static void checkEqual(double value, double expectedValue) { + checkEqual(value, expectedValue, "compare"); + } + + /** + * Compared value and expectedValue + * and fails with message if they aren't equal. + * + * @param value Unkown value. + * @param expectedValue Expected value. + * @param msg Message with which to fail. + */ + public static void checkEqual(double value, double expectedValue, String msg) { + if (Double.isNaN(value) && Double.isNaN(expectedValue)) return; + if (value == expectedValue) return; + if (value < expectedValue) { + msg = msg+": "+value+" < "+expectedValue; + } else { + msg = msg+": "+value+" > "+expectedValue; + } + checkFailed(msg); + } + + /** + * Compares value and expectedValue + * with failing message "compare". + * + * @param value Unkown value. + * @param expectedValue Expected value. + * @see #checkEqual(short,short,String) + */ + public static void checkEqual(short value, short expectedValue) { + checkEqual(value, expectedValue, "compare"); + } + + /** + * Compared value and expectedValue + * and fails with message if they aren't equal. + * + * @param value Unkown value. + * @param expectedValue Expected value. + * @param msg Message with which to fail. + */ + public static void checkEqual(short value, short expectedValue, String msg) { + if (value == expectedValue) return; + if (value < expectedValue) { + msg = msg+": "+value+" < "+expectedValue; + } else { + msg = msg+": "+value+" > "+expectedValue; + } + checkFailed(msg); + } + + /** + * Compares value and expectedValue + * with failing message "compare". + * + * @param value Unkown value. + * @param expectedValue Expected value. + * @see #checkEqual(byte,byte,String) + */ + public static void checkEqual(byte value, byte expectedValue) { + checkEqual(value, expectedValue, "compare"); + } + + /** + * Compares value and expectedValue + * with failing message msg. + * + * @param value Unkown value. + * @param expectedValue Expected value. + * @param msg Message with which to fail. + */ + public static void checkEqual(byte value, byte expectedValue, String msg) { + if (value == expectedValue) return; + if (value < expectedValue) { + msg = msg+": "+value+" < "+expectedValue; + } else { + msg = msg+": "+value+" > "+expectedValue; + } + checkFailed(msg); + } + + /** + * Compares value and expectedValue + * with failing message "compare". + * + * @param value Unkown value. + * @param expectedValue Expected value. + * @see #checkEqual(char,char,String) + */ + public static void checkEqual(char value, char expectedValue) { + checkEqual(value, expectedValue, "compare"); + } + + /** + * Compares value and expectedValue + * with failing message msg. + * + * @param value Unkown value. + * @param expectedValue Expected value. + * @param msg Message with which to fail. + */ + public static void checkEqual(char value, char expectedValue, String msg) { + if (value == expectedValue) return; + if (value < expectedValue) { + msg = msg+": "+value+" < "+expectedValue; + } else { + msg = msg+": "+value+" > "+expectedValue; + } + checkFailed(msg); + } + + /** + * Compares value and expectedValue + * with failing message "compare". + * + * @param value Unkown value. + * @param expectedValue Expected value. + * @see #checkEqual(boolean,boolean,String) + */ + public static void checkEqual(boolean value, boolean expectedValue) { + checkEqual(value, expectedValue, "compare"); + } + + /** + * Compares value and expectedValue + * with failing message msg. + * + * @param value Unkown value. + * @param expectedValue Expected value. + * @param msg Message with which to fail. + */ + public static void checkEqual(boolean value, boolean expectedValue, String msg) { + if (value == expectedValue) return; + msg = msg+": "+value+" != "+expectedValue; + checkFailed(msg); + } + + /** + * Checks whether the entries of set are equal + * using equals to the corresponding String in + * expectedSet and fails with message msg. + * + * @param set Unkown set of values. + * @param expectedSet Expected String of values. + * @param msg Message with which to fail. + */ + public static void checkEqual(Collection set, String expectedSet, String msg) { + checkEqual(set, LangUtil.split(expectedSet), msg); + } + + /** + * Checks whether the entries of set are equal + * using equals to the corresponding entry in + * expectedSet and fails with message msg, + * except that duplicate actual entries are ignored. + * This issues fail messages for each failure; when + * aborting on failure, only the first will be reported. + * + * @param set Unkown set of values. + * @param expectedSet Expected String of values. + * @param msg Message with which to fail. + */ + public static void checkEqualIgnoreDups(Collection set, String[] expected, String msg, + boolean ignoreDups) { + String[] diffs = diffIgnoreDups(set, expected, msg, ignoreDups); + if (0 < diffs.length) { + check(false, "" + Arrays.asList(diffs)); + } +// for (int i = 0; i < diffs.length; i++) { +// check(false, diffs[i]); +// } + } + + /** @return String[] of differences '{un}expected msg "..." {not} found' */ + private static String[] diffIgnoreDups(Collection set, String[] expected, String msg, + boolean ignoreDups) { + ArrayList result = new ArrayList(); + ArrayList actual = new ArrayList(set); + BitSet hits = new BitSet(); + for (int i = 0; i < expected.length; i++) { + if (!actual.remove(expected[i])) { + result.add(" expected " + msg + " \"" + expected[i] + "\" not found"); + } else { + hits.set(i); + if (ignoreDups) { + while (actual.remove(expected[i])) ; // remove all instances of it + } + } + } + for (Iterator iter = actual.iterator(); iter.hasNext();) { + String act = (String) iter.next(); + result.add(" unexpected " + msg + " \"" + act + "\" found"); + } + return (String[]) result.toArray(new String[0]); + } + + /** + * Checks whether the entries of set are equal + * using equals to the corresponding entry in + * expectedSet and fails with message msg. + * + * @param set Unkown set of values. + * @param expectedSet Expected String of values. + * @param msg Message with which to fail. + */ + public static void checkEqual(Collection set, String[] expected, String msg) { + checkEqualIgnoreDups(set, expected, msg, false); + } + + /** + * Compares value and expectedValue + * with failing message "compare". + * + * @param value Unkown value. + * @param expectedValue Expected value. + * @see #checkEqual(Object,Object,String) + */ + public static void checkEqual(Object value, Object expectedValue) { + checkEqual(value, expectedValue, "compare"); + } + + /** + * Checks whether the entries of set are equal + * using equals to the corresponding String in + * expectedSet and fails with message msg. + * + * @param set Unkown set of values. + * @param expectedSet Expected String of values. + * @param msg Message with which to fail. + */ + public static void checkEqual(Object value, Object expectedValue, String msg) { + if (value == null && expectedValue == null) return; + if (value != null && value.equals(expectedValue)) return; + msg = msg+": "+value+" !equals "+expectedValue; + checkFailed(msg); + } + + /** + * Checks whether the entries of set are equal + * using equals to the corresponding String in + * expectedSet and fails with message msg. + * + * @param set Unkown set of values. + * @param expectedSet Expected String of values. + * @param msg Message with which to fail. + */ + public static void checkEq(Object value, Object expectedValue, String msg) { + if (value == expectedValue) return; + msg = msg+": "+value+" != "+expectedValue; + checkFailed(msg); + } + + /** add expected events */ + public static void expectEvent(String s) { + if (null != s) { + expectedEvents.add(s); + } + } + + /** add expected events */ + public static void expectEvent(Object s) { + if (null != s) { + expectEvent(s.toString()); + } + } + + /** + * add expected events, parse out ; from string + * Expect those messages in s separated by + * ":;, ". + * + * @param s String containg delimited,expected messages. + */ + public static void expectEventsInString(String s) { + if (null != s) { + StringTokenizer tok = new StringTokenizer(s, ":;, "); + while (tok.hasMoreTokens()) { + expectEvent(tok.nextToken()); + } + } + } + + public static void expectEventsInString(String[] ra) { + expectEvents((Object[]) ra); + } + + /** add expected events */ + public static void expectEvents(Object[] events) { + if (null != events) { + for (int i = 0; i < events.length; i++) { + if (null != events[i]) { + expectEvent(events[i].toString()); + } + } + } + } + + /** add expected events */ + public static void expectEvents(String[] events) { + if (null != events) { + for (int i = 0; i < events.length; i++) { + if (null != events[i]) { + expectEvent(events[i].toString()); + } + } + } + } + + /** check actual and expected have same members */ + public static void checkAllEvents() { + checkAndClearEvents((String[]) expectedEvents.toArray(new String[0])); + } + + /** also ignore duplicate actual entries for expected */ + public static void checkAllEventsIgnoreDups() { + final boolean ignoreDups = true; + final String[] exp = (String[]) expectedEvents.toArray(new String[0]); + checkEqualIgnoreDups(actualEvents, exp, "event", ignoreDups); + clearEvents(); + } + + /** Check events, file is line-delimited. If there is a non-match, signalls + * a single error for the first event that does not match the next event in + * the file. The equivalence is {@link #checkEqualLists}. Blank lines are + * ignored. lines that start with '//' are ignored. */ + public static void checkEventsFromFile(String eventsFile) { + // XXX bug reads into current expected and checks all - separate read and check + try { + File file = new File(getBASEDIR(), eventsFile); // XXX TestDriver + BufferedReader in = new BufferedReader(new FileReader(file)); + //final File parentDir = (null == file? null : file.getParentFile()); + String line; + List expEvents = new ArrayList(); + while ((line = in.readLine()) != null) { + line = line.trim(); + if ((line.length() < 1) || (line.startsWith("//"))) continue; + expEvents.add(line); + } + checkEqualLists(actualEvents, expEvents, " from " + eventsFile); + } catch (IOException ioe) { + throwable(ioe); + } + } + + + /** Check to see that two lists of strings are the same. Order is important. + * Trimmable whitespace is not important. Case is important. + * + * @param actual one list to check + * @param expected another list + * @param message a context string for the resulting error message if the test fails. + */ + public static void checkEqualLists(List/*String*/ actual, List/*String*/ expected, + String message) { + Iterator a = actual.iterator(); + Iterator e = expected.iterator(); + int ai = 0; + int ei = 0; + for (; a.hasNext(); ) { + if (! e.hasNext()) { + checkFailed("unexpected [" + ai + "] \"" + a.next() + "\" " + message); + return; + } + String a0 = ((String) a.next()).trim(); + String e0 = ((String) e.next()).trim(); + if (! a0.equals(e0)) { + checkFailed("expected [" + ei + "] \"" + e0 + + "\"\n but found [" + ai + "] \"" + a0 + "\"\n " + message); + return; + } + ai++; + ei++; + } + while (e.hasNext()) { + checkFailed("expected [" + ei + "] \"" + e.next() + "\" " + message); + ei++; + } + } + + /** Check events, expEvents is space delimited */ + public static void checkEvents(String expEvents) { + checkEqual(actualEvents, expEvents, "event"); + } + + /** Check events, expEvents is an array */ + public static void checkEvents(String[] expEvents) { + checkEqual(actualEvents, expEvents, "event"); + } + + /** Check events and clear after check*/ + public static void checkAndClearEvents(String expEvents) { + checkEvents(expEvents); + clearEvents(); + } + + /** Check events and clear after check*/ + public static void checkAndClearEvents(String[] expEvents) { + checkEvents(expEvents); + clearEvents(); + } + + /** XXX deprecated */ + public static void printEvents() { // XXX no clients? + for (Iterator i = actualEvents.iterator(); i.hasNext(); ) { + System.out.println(i.next()); // XXX System.out + } + } + + /** + * Report an uncaught exeption as an error + * @param thrown Throwable to print. + * @see #maxStackTrace + */ + public void unexpectedExceptionFailure(Throwable thrown) { + handle("unexpectedExceptionFailure", thrown, true); + } + + /** + * Handle message by delegation to message handler, doing + * IMessage.FAIL if (fail || (thrown != null) and IMessage.INFO + * otherwise. + */ + + private static void handle(String message, Throwable thrown, boolean fail) { + final boolean failed = fail || (null != thrown); + IMessage.Kind kind = (failed ? IMessage.FAIL : IMessage.INFO); + IMessage m = new Message(message, kind, thrown, null); + final boolean handled = messageHandler.handleMessage(m); + } +// private static void resofhandle(String message, Throwable thrown, boolean fail) { +// /* If FAIL and the message handler returns false (normally), +// * Then this preserves "abort" semantics by throwing an +// * abort exception. +// */ +// if (failed) { +// if (handled) { +// String s = "Tester expecting handler to return false or " +// + "complete abruptly when passed a fail, for " + m; +// m = new Message(s, IMessage.DEBUG, null, null); +// messageHandler.handleMessage(m); +// } else { +// throw AbortException.borrowPorter(m); +// } +// } +// } + +} diff --git a/testing-client/testsrc/TestingClientModuleTests.java b/testing-client/testsrc/TestingClientModuleTests.java new file mode 100644 index 000000000..7148e03e5 --- /dev/null +++ b/testing-client/testsrc/TestingClientModuleTests.java @@ -0,0 +1,31 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +// default package + +import junit.framework.*; +import junit.framework.Test; + +public class TestingClientModuleTests extends TestCase { + + public static Test suite() { + TestSuite suite = new TestSuite(TestingClientModuleTests.class.getName()); + suite.addTestSuite(TestingClientModuleTests.class); // minimum 1 test (testNothing) + return suite; + } + + public TestingClientModuleTests(String name) { super(name); } + + public void testNothing() {} +} diff --git a/testing-util/.classpath b/testing-util/.classpath new file mode 100644 index 000000000..c6752e35d --- /dev/null +++ b/testing-util/.classpath @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/testing-util/.project b/testing-util/.project new file mode 100644 index 000000000..917eccbff --- /dev/null +++ b/testing-util/.project @@ -0,0 +1,19 @@ + + + testing-util + + + bridge + util + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/testing-util/TestUtilTest.launch b/testing-util/TestUtilTest.launch new file mode 100644 index 000000000..645c3ee88 --- /dev/null +++ b/testing-util/TestUtilTest.launch @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/testing-util/src/.cvsignore b/testing-util/src/.cvsignore new file mode 100644 index 000000000..a3f0b1b77 --- /dev/null +++ b/testing-util/src/.cvsignore @@ -0,0 +1 @@ +*.lst diff --git a/testing-util/src/org/aspectj/testing/util/TestUtil.java b/testing-util/src/org/aspectj/testing/util/TestUtil.java new file mode 100644 index 000000000..b33e1fac9 --- /dev/null +++ b/testing-util/src/org/aspectj/testing/util/TestUtil.java @@ -0,0 +1,616 @@ +/* ******************************************************************* + * Copyright (c) 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.util; + +import org.aspectj.bridge.IMessageHandler; +import org.aspectj.bridge.MessageUtil; +import org.aspectj.util.FileUtil; +import org.aspectj.util.LangUtil; +import org.aspectj.util.Reflection; + +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; +import java.io.StringReader; +import java.io.StringWriter; +import java.io.Writer; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import jdiff.text.FileLine; +import jdiff.util.Diff; +import jdiff.util.DiffNormalOutput; +import junit.framework.Assert; +import junit.framework.TestCase; +import junit.runner.TestCaseClassLoader; + +/** + * Things that junit should perhaps have, but doesn't. + * Note the file-comparison methods require JDiff to run, + * but JDiff types are not required to resolve this class. + * Also, the bytecode weaver is required to compare class + * files, but not to compare other files. + */ + +public final class TestUtil { + + private TestUtil() { + super(); + } + + // ---- arrays + + public static void assertArrayEquals(String msg, Object[] expected, Object[] found) { + TestCase.assertEquals(msg, Arrays.asList(expected), Arrays.asList(found)); + } + + // ---- unordered + + public static void assertSetEquals(Collection expected, Collection found) { + assertSetEquals(null, expected, found); + } + + public static void assertSetEquals(String msg, Object[] expected, Object[] found) { + assertSetEquals(msg, Arrays.asList(expected), Arrays.asList(found)); + } + + public static void assertSetEquals( + String msg, + Collection expected, + Collection found) { + msg = (msg == null) ? "" : msg + ": "; + + Set results1 = new HashSet(found); + results1.removeAll(expected); + + Set results2 = new HashSet(expected); + results2.removeAll(found); + + if (results1.isEmpty()) { + TestCase.assertTrue( + msg + "Expected but didn't find: " + results2.toString(), + results2.isEmpty()); + } else if (results2.isEmpty()) { + TestCase.assertTrue( + msg + "Didn't expect: " + results1.toString(), + results1.isEmpty()); + } else { + TestCase.assertTrue( + msg + + "Expected but didn't find: " + + results2.toString() + + "\nDidn't expect: " + + results1.toString(), + false); + } + } + + // ---- objects + + public static void assertCommutativeEquals(Object a, Object b, boolean should) { + TestCase.assertEquals(a + " equals " + b, should, a.equals(b)); + TestCase.assertEquals(b + " equals " + a, should, b.equals(a)); + assertHashEquals(a, b, should); + } + + private static void assertHashEquals(Object s, Object t, boolean should) { + if (should) { + TestCase.assertTrue( + s + " does not hash to same as " + t, + s.hashCode() == t.hashCode()); + } else { + if (s.hashCode() == t.hashCode()) { + System.err.println("warning: hash collision with hash = " + t.hashCode()); + System.err.println(" for " + s); + System.err.println(" and " + t); + } + } + } + + // -- reflective stuff + + public static void runMain(String classPath, String className) { + runMethod(classPath, className, "main", new Object[] { new String[0] }); + } + + + public static Object runMethod(String classPath, String className, String methodName, Object[] args) { + classPath += File.pathSeparator + System.getProperty("java.class.path"); + + ClassLoader loader = new TestCaseClassLoader(classPath); + + Class c=null; + try { + c = loader.loadClass(className); + } catch (ClassNotFoundException e) { + Assert.assertTrue("unexpected exception: " + e, false); + } + return Reflection.invokestaticN(c, methodName, args); + } + + + /** + * Checks that two multi-line strings have the same value. + * Each line is trimmed before comparision + * Produces an error on the particular line of conflict + */ + public static void assertMultiLineStringEquals(String message, String s1, String s2) { + try { + BufferedReader r1 = new BufferedReader(new StringReader(s1)); + BufferedReader r2 = new BufferedReader(new StringReader(s2)); + + + List lines = new ArrayList(); + String l1, l2; + + int index = 1; + while(true) { + l1 = readNonBlankLine(r1); + l2 = readNonBlankLine(r2); + if (l1 == null || l2 == null) break; + if (l1.equals(l2)) { + lines.add(l1); + } else { + showContext(lines); + Assert.assertEquals(message +"(line " + index +")", l1, l2); + } + index++; + } + if (l1 != null) showContext(lines); + Assert.assertTrue(message + ": unexpected " + l1, l1 == null); + if (l2 != null) showContext(lines); + Assert.assertTrue(message + ": unexpected " + l2, l2 == null); + } catch (IOException ioe) { + Assert.assertTrue(message + ": caught " + ioe.getMessage(), false); + } + } + + private static void showContext(List lines) { + int n = lines.size(); + for (int i = Math.max(0, n - 8); i < n; i++) { + System.err.println(lines.get(i)); + } + } + + private static String readNonBlankLine(BufferedReader r) throws IOException { + String l = r.readLine(); + if (l == null) return null; + l = l.trim(); + // comment to include comments when reading + int commentLoc = l.indexOf("//"); + if (-1 != commentLoc) { + l = l.substring(0, commentLoc).trim(); + } + if ("".equals(l)) return readNonBlankLine(r); + return l; + } + + /** + * If there is an expected dir, expect each file in its subtree + * to match a corresponding actual file in the base directory. + * @return boolean + */ + public static boolean sameDirectoryContents( + final IMessageHandler handler, + final File expectedBaseDir, + final File actualBaseDir, + final boolean fastFail) { + LangUtil.throwIaxIfNull(handler, "handler"); + FileUtil.throwIaxUnlessCanReadDir(actualBaseDir, "actualBaseDir"); + if (!FileUtil.canReadDir(expectedBaseDir)) { + MessageUtil.fail(handler, " expected dir not found: " + expectedBaseDir); + return false; + } + if (!FileUtil.canReadDir(actualBaseDir)) { + MessageUtil.fail(handler, " actual dir not found: " + actualBaseDir); + return false; + } + String[] paths = FileUtil.listFiles(expectedBaseDir); + boolean result = true; + for (int i = 0; i < paths.length; i++) { + if (!sameFiles(handler, expectedBaseDir, actualBaseDir, paths[i]) && !result) { + result = false; + if (fastFail) { + break; + } + } + } + return result; + } + + //------------ File-comparison utilities (XXX need their own class...) + /** + * Compare two files, line by line, and report differences as one FAIL message + * if a handler is supplied. This preprocesses .class files by disassembling. + * @param handler the IMessageHandler for any FAIL messages (null to ignore) + * @param expectedFile the File path to the canonical file + * @param actualFile the File path to the actual file, if any + * @return true if the input files are the same, based on per-line comparisons + */ + public static boolean sameFiles ( + IMessageHandler handler, + File expectedFile, + File actualFile) { + return doSameFile(handler, null, null, expectedFile, actualFile); + } + + /** + * Compare two files, line by line, and report differences as one FAIL message + * if a handler is supplied. This preprocesses .class files by disassembling. + * This method assumes that the files are at the same offset from two + * respective base directories. + * @param handler the IMessageHandler for any FAIL messages (null to ignore) + * @param expectedBaseDir the File path to the canonical file base directory + * @param actualBaseDir the File path to the actual file base directory + * @param path the String path offset from the base directories + * @return true if the input files are the same, based on per-line comparisons + */ + public static boolean sameFiles ( + IMessageHandler handler, + File expectedBaseDir, + File actualBaseDir, + String path) { + File actualFile = new File(actualBaseDir, path); + File expectedFile = new File(expectedBaseDir, path); + return doSameFile(handler, expectedBaseDir, actualBaseDir, expectedFile, actualFile); + } + + /** + * This does the work, selecting a lineator subclass and converting public + * API's to JDiff APIs for comparison. + * Currently, all jdiff interfaces are method-local, so this class with load + * without it; if we do use it, we can avoid the duplication. + */ + private static boolean doSameFile( + IMessageHandler handler, + File expectedBaseDir, + File actualBaseDir, + File expectedFile, + File actualFile) { + String path = expectedFile.getPath(); + // XXX permit user to specify lineator + ILineator lineator = Lineator.TEXT; + if (path.endsWith(".class")) { + if (ClassLineator.haveDisassembler() ) { + lineator = Lineator.CLASS; + } else { + MessageUtil.abort(handler, "skipping - dissassembler not available"); + return false; + } + } + CanonicalLine[] actualLines = null; + CanonicalLine[] expectedLines = null; + try { + actualLines = lineator.getLines(handler, actualFile, actualBaseDir); + expectedLines = lineator.getLines(handler, expectedFile, expectedBaseDir); + } catch (IOException e) { + MessageUtil.fail(handler, "rendering lines ", e); + return false; + } + if (!LangUtil.isEmpty(actualLines) && !LangUtil.isEmpty(expectedLines)) { + // here's the transmutation back to jdiff - extract if publishing JDiff + CanonicalLine[][] clines = new CanonicalLine[][] { expectedLines, actualLines }; + FileLine[][] flines = new FileLine[2][]; + for (int i = 0; i < clines.length; i++) { + CanonicalLine[] cline = clines[i]; + FileLine[] fline = new FileLine[cline.length]; + for (int j = 0; j < fline.length; j++) { + fline[j] = new FileLine(cline[j].canonical, cline[j].line); + } + flines[i] = fline; + } + + Diff.change edits = new Diff(flines[0], flines[1]).diff_2(false); + if ((null == edits) || (0 == (edits.inserted + edits.deleted))) { + // XXX confirm with jdiff that null means no edits + return true; + } else { + //String m = render(handler, edits, flines[0], flines[1]); + StringWriter writer = new StringWriter(); + DiffNormalOutput out = new DiffNormalOutput(flines[0], flines[1]); + out.setOut(writer); + out.setLineSeparator(LangUtil.EOL); + try { + out.writeScript(edits); + } catch (IOException e) { + MessageUtil.fail(handler, "rendering edits", e); + } finally { + if (null != writer) { + try { writer.close(); } + catch (IOException e) { + MessageUtil.fail(handler, "closing after rendering edits", e); + } + } + } + String message = "diff between " + + path + + " in expected dir " + + expectedBaseDir + + " and actual dir " + + actualBaseDir + + LangUtil.EOL + + writer.toString(); + MessageUtil.fail(handler, message); + } + } + return false; + } + + + /** component that reduces file to CanonicalLine[] */ + public static interface ILineator { + /** Lineator suitable for text files */ + public static final ILineator TEXT = new TextLineator(); + + /** Lineator suitable for class files (disassembles first) */ + public static final ILineator CLASS = new ClassLineator(); + + /** + * Reduce file to CanonicalLine[]. + * @param handler the IMessageHandler for errors (may be null) + * @param file the File to render + * @param basedir the File for the base directory (may be null) + * @return CanonicalLine[] of lines - not null, but perhaps empty + */ + CanonicalLine[] getLines( + IMessageHandler handler, + File file, + File basedir) throws IOException; + } + + /** alias for jdiff FileLine to avoid client binding */ + public static class CanonicalLine { + public static final CanonicalLine[] NO_LINES = new CanonicalLine[0]; + + /** canonical variant of line for comparison */ + public final String canonical; + + /** actual line, for logging */ + public final String line; + public CanonicalLine(String canonical, String line) { + this.canonical = canonical; + this.line = line; + } + public String toString() { + return line; + } + } + + private abstract static class Lineator implements ILineator { + /** + * Reduce file to CanonicalLine[]. + * @param handler the IMessageHandler for errors (may be null) + * @param file the File to render + * @param basedir the File for the base directory (may be null) + */ + public CanonicalLine[] getLines( + IMessageHandler handler, + File file, + File basedir) + throws IOException { + + if (!file.canRead() || !file.isFile()) { + MessageUtil.error(handler, "not readable file: " + basedir + " - " + file); + return null; + } + // capture file as FileLine[] + InputStream in = null; + String path = FileUtil.normalizedPath(file, basedir); + LineStream capture = new LineStream(); + try { + lineate(capture, handler, basedir, file); + } catch (IOException e) { + MessageUtil.fail(handler, + "NormalizedCompareFiles IOException reading " + file, e); + return null; + } finally { + if (null != in) { + try { in.close(); } + catch (IOException e) {} // ignore + } + capture.flush(); + capture.close(); + } + String missed = capture.getMissed(); + if (!LangUtil.isEmpty(missed)) { + MessageUtil.warn(handler, + "NormalizedCompareFiles missed input: " + + missed); + return null; + } else { + String[] lines = capture.getLines(); + CanonicalLine[] result = new CanonicalLine[lines.length]; + for (int i = 0; i < lines.length; i++) { + result[i] = new CanonicalLine(lines[i], lines[i]); + } + return result; + } + } + + protected abstract void lineate( + PrintStream sink, + IMessageHandler handler, + File basedir, + File file) throws IOException; + } + + private static class TextLineator extends Lineator { + + protected void lineate( + PrintStream sink, + IMessageHandler handler, + File basedir, + File file) throws IOException { + InputStream in = null; + try { + in = new FileInputStream(file); + FileUtil.copyStream(new DataInputStream(in), sink); + } finally { + try { in.close(); } + catch (IOException e) {} // ignore + } + } + } + + public static class ClassLineator extends Lineator { + + protected void lineate( + PrintStream sink, + IMessageHandler handler, + File basedir, + File file) throws IOException { + String name = FileUtil.fileToClassName(basedir, file); + // XXX re-enable preflight? +// if ((null != basedir) && (path.length()-6 != name.length())) { +// MessageUtil.error(handler, "unexpected class name \"" +// + name + "\" for path " + path); +// return null; +// } + disassemble(handler, basedir, name, sink); + } + + public static boolean haveDisassembler() { + try { + return (null != Class.forName("org.aspectj.weaver.bcel.LazyClassGen")); + } catch (ClassNotFoundException e) { + // XXX fix + //System.err.println(e.getMessage()); + //e.printStackTrace(System.err); + return false; + } + } + + /** XXX dependency on bcweaver/bcel */ + private static void disassemble( + IMessageHandler handler, + File basedir, + String name, + PrintStream out) throws IOException { + // LazyClassGen.disassemble(FileUtil.normalizedPath(basedir), name, capture); + + Throwable thrown = null; + String basedirPath = FileUtil.normalizedPath(basedir); + // XXX use reflection utilities to invoke dissassembler? + try { + // XXX need test to detect when this is refactored + Class c = Class.forName("org.aspectj.weaver.bcel.LazyClassGen"); + Method m = c.getMethod("disassemble", + new Class[] {String.class, String.class, PrintStream.class}); + m.invoke(null, new Object[] { basedirPath, name, out}); + } catch (ClassNotFoundException e) { + thrown = e; + } catch (NoSuchMethodException e) { + thrown = e; + } catch (IllegalAccessException e) { + thrown = e; + } catch (InvocationTargetException e) { + Throwable t = e.getTargetException(); + if (t instanceof IOException) { + throw (IOException) t; + } + thrown = t; + } + if (null != thrown) { + MessageUtil.fail(handler, "disassembling " + name + " path: " + basedirPath, + thrown); + } + } + } + + + /** + * Capture PrintStream output to String[] + * (delimiting component String on println()), + * also showing any missed text. + */ + public static class LineStream extends PrintStream { + StringBuffer sb = new StringBuffer(); + ByteArrayOutputStream missed; + ArrayList sink; + public LineStream() { + super(new ByteArrayOutputStream()); + this.sink = new ArrayList(); + missed = (ByteArrayOutputStream) out; + } + + /** @return any text not captured by our overrides */ + public String getMissed() { + return missed.toString(); + } + + /** clear captured lines (but not missed text) */ + public void clear() { + sink.clear(); + } + + /** + * Get String[] of lines printed, + * delimited by println(..) calls. + * @return lines printed, exclusive of any not yet terminated by newline + */ + public String[] getLines() { + return (String[]) sink.toArray(new String[0]); + } + + // ---------- PrintStream overrides + public void println(Object x) { + println(x.toString()); + } + + public void print(Object obj) { + print(obj.toString()); + } + + public void println(char c) { + sb.append(c); + println(); + } + public void println(char[] c) { + sb.append(c); + println(); + } + public void print(char c) { + sb.append(c); + } + + public void print(char[] c) { + sb.append(c); + } + + public void println(String s) { + print(s); + println(); + } + public void print(String s) { + sb.append(s); + } + public void println() { + String line = sb.toString(); + sink.add(line); + sb.setLength(0); + } + } + + +} diff --git a/testing-util/testdata/testCompareClassFiles/org/aspectj/testing/util/TestCompareClassFile$1.class b/testing-util/testdata/testCompareClassFiles/org/aspectj/testing/util/TestCompareClassFile$1.class new file mode 100644 index 0000000000000000000000000000000000000000..c5db4e907bc0871394784737e8941612fe167c2c GIT binary patch literal 463 zcmb7AyH3ME5S+E07>prcXy_?;2uwr?B84R+Bnu>n(x1&KoaLOOJDdNaLZaXU_$b8M zM7W`$x!u{D*_*k~ulEnQIK>_zuFO0Zb}93^h-+zUrRQ-|t1_N4NUFtB7@3s9+8b5M zgRu`HY!~81#HC>BeO9p4hf9e1=|iKn$V%yBKp0*rt?Fxndw4V>cuDmnL-<$=;2|K4 zQl;fxv&f{Gwj$n?R=Fr=!l)LnhF<-wEMc7f)A1!gY#L1n9;?RWvdux*|0~{En}87B zYAsC{T3VdLVV@iW;0~>EM(f;Na3!vT<2P))cGn@FK}VZ(qD{u(4+&csG2-9*U2xK2 cp7r8kN1mV;(n{!Hhv^O?>~am5*SZ0sZ^$rVApigX literal 0 HcmV?d00001 diff --git a/testing-util/testdata/testCompareClassFiles/org/aspectj/testing/util/TestCompareClassFile.class b/testing-util/testdata/testCompareClassFiles/org/aspectj/testing/util/TestCompareClassFile.class new file mode 100644 index 0000000000000000000000000000000000000000..52c60e6eedb52f8f3589ae96703cd32f074c7bba GIT binary patch literal 4094 zcmb7H`+M8u6@Fzqk>jd2Q@4b)EbACe94Dc`GDf&GqzSmQ6I#1TFN0QblthUwd$OE1 zEtgWtfN{TVYoWk^5!i)MwmL1@x^821u4t9Ip`Z<$LI@zHU>TGK_|PGt^Gk8&y3I0f2w}7Eu^cOekDCaTH6f7gOnfO{ zY_6YWN(j|T<3p_|^37WgAICK-p%<;VkyHy3ZfbETVKbbPZVy9T{|s|Zr9!`QgA1>EW;h>sc^^kCA`b%Wn*jpdrVWEaSr-I=tqp93MGcR zWsy)NJA(B05vtmx+lMmt5g=jD&6qdDo|q|U3RGdSA0rah)htYHEX%>_ zw0kOJlv~Zv4#e$Mm*K20V2=|!?oG!{Siu;^L(njB$qI1KWlVDVRY4pFis&L#iC@l@ zc)WE;K^8f&)i_i2#eB&Wc}d(Lt3CQoYE&?_B6kM?(6aid)@O`zeTxmoL0<2T=`8lU zN@HW1+07{2#WhsChcc$=Hme!q7K`JWjVm~anGg=(kc2fgHZaPRHtNj910g&p2+>$O zc6_GuQ<|Y4(o7i-O9)zdM@&Zxy6i~V=0|W;M0!+$FSI;}$0TgJyb3R^T}uI58y*kg z2%aGFhIY`hLo4{c@OT7Iu@-fd{f>D3RXDjv!83SP6!9Ea%Y<4?=1t9ZKpcqy&tJfc z!p}<*WO6N`im_Z+M3oz_;0MCh580JVF_v+h%hA+w`Dsn~Q1Ggw{2KdJ1FvVKXqjq; zl+Fse>Ul%P>x`@y`*HX-dLhjwx&JN>iz)rp{$atH{_YEc#`~<-Uyn}Z` zI3cUWb?Fdju?hN1d?>nfiigeoYw76dXb$36 zmw>yVHK&C^naolP1J2EH5v2cGNDy-+8^9d3(c}|^ts-zdIQ21ck z_IR>vnf=(8=o=v3R^C#x6=Xvxd(y=l9ecXii(ZGI2Y)@|hM}4E{A)#gl|S79&_7=u z8zCde`v!p05I=lB;C)#9#`AjxPgY_THCFR!yKM<9JA-v+uwhI1D+8x&!)9C6&ufeh z+v%u-VSr|apgK0kZBv!54%WvjaO^{B`OwVk zTGa76NEGiSN4FEZ9h9f-NDlEfB7r1z=obUzG6#rSgVgttc-yIerRZrl`VgZJe?jzJ z^thYR_t5WNM!)a>h`x)_8)>!2jXusgMC}`*W!zszCeao>cLw@-OeLfKm_Npcyr*o3 zKoguEontzQCQNyPZa{%D@HB$&!eodrQ!G;Eqs&6d2`ary0tTt~dGu3+82IoB1WwK4 zLDu_Favn!^Q+6!;y*WHNFbgHtUdGd9JU`6umyaWKOuE=~^5TD_0e@HBEY`=$_|bWs zxHZrixPbaugpbupl${*r<*nlv{}rh_iC|OUE!_VRX3Kc5tKQ!w&*GYh9I2 0) || (maxLines < 1)) { + final int EOL_LEN = LangUtil.EOL.length(); + int totalLength = 0; + while (!lines.isEmpty()) { + totalLength += EOL_LEN + ((String) lines.getFirst()).length(); + lines.removeFirst(); + } + if (stack.length() > totalLength) { + stack.setLength(totalLength); + if (elided > 0) { + stack.append(" (... " + elided + " lines...)"); + } + } + } + } + +} diff --git a/testing-util/testsrc/org/aspectj/testing/util/TestUtilTest.java b/testing-util/testsrc/org/aspectj/testing/util/TestUtilTest.java new file mode 100644 index 000000000..73da9cf83 --- /dev/null +++ b/testing-util/testsrc/org/aspectj/testing/util/TestUtilTest.java @@ -0,0 +1,116 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.util; + +import org.aspectj.bridge.IMessageHolder; +import org.aspectj.bridge.MessageHandler; +import org.aspectj.bridge.MessageUtil; +import org.aspectj.util.FileUtil; + +import java.io.File; +import java.io.IOException; + +import junit.framework.TestCase; + +/** + * + */ +public class TestUtilTest extends TestCase { + + public TestUtilTest(String name) { + super(name); + } + + public void testFileCompareNonClass() throws IOException { + MessageHandler holder = new MessageHandler(); + File thisFile = new File("testsrc/org/aspectj/testing/util/TestUtilTest.java"); + //File thisFile = new File("src/testing-util.lst"); + assertTrue(TestUtil.sameFiles(holder, thisFile, thisFile)); + + File tempFile = File.createTempFile("TestUtilTest", ".tmp"); + FileUtil.copyFile(thisFile, tempFile); + long len = tempFile.length(); + assertTrue(0 != len); + long tlen = thisFile.length(); + assertEquals(tlen, len); + assertTrue(TestUtil.sameFiles(holder, tempFile, thisFile)); + try { + String path = thisFile.getName(); + File basedir = tempFile.getParentFile(); + File renamed = new File(basedir, path); + if (!tempFile.renameTo(renamed)) { + MessageUtil.warn(holder, "unable to rename " + tempFile + " to " + renamed); + } else { + len = renamed.length(); + assertEquals(tlen, len); + assertTrue(TestUtil.sameFiles(holder, basedir, thisFile.getParentFile(), path)); + } + } finally { + if (0 < holder.numMessages(null, true)) { + MessageUtil.print(System.out, holder); + holder.clearMessages(); + } + tempFile.delete(); + } + } + + public void testFileCompareNonClassStaticPositive() throws IOException { + MessageHandler holder = new MessageHandler(); + File basedir = new File("testdata/testCompareTextFiles/sameFile"); + File expectedBaseDir = new File(basedir, "expected"); + File actualBaseDir = new File(basedir, "actual"); + String filename = "TestUtilTest.java"; + File expected = new File(expectedBaseDir, filename); + File actual = new File(actualBaseDir, filename); + + assertTrue(TestUtil.sameFiles(holder, expected, actual)); + + assertTrue(TestUtil.sameFiles(holder, expectedBaseDir, actualBaseDir, filename)); + } + + public void testFileCompareNonClassStaticNegative() throws IOException { + MessageHandler holder = new MessageHandler(); + File basedir = new File("testdata/testCompareTextFiles/differentFile"); + File expectedBaseDir = new File(basedir, "expected"); + File actualBaseDir = new File(basedir, "actual"); + String filename = "TestUtilTest.java"; + File expected = new File(expectedBaseDir, filename); + File actual = new File(actualBaseDir, filename); + + assertTrue(!TestUtil.sameFiles(holder, expected, actual)); + + assertTrue(!TestUtil.sameFiles(holder, expectedBaseDir, actualBaseDir, filename)); + } + + public void testFileCompareClass() throws IOException { + if (!TestUtil.ClassLineator.haveDisassembler()) { + System.err.println("skipping testFileCompareClass - no disassembler on classpath"); + return; + } + MessageHandler holder = new MessageHandler(); + File classBase = new File("testdata/testCompareClassFiles"); + String path = "org/aspectj/testing/util/TestCompareClassFile.class"; + File classFile = new File(classBase, path); + + try { + assertTrue(TestUtil.sameFiles(holder, classFile, classFile)); + assertTrue(TestUtil.sameFiles(holder, classBase, classBase, path)); + } finally { + if (0 < holder.numMessages(null, true)) { + MessageUtil.print(System.out, holder); + } + } + } + +} diff --git a/testing-util/testsrc/org/aspectj/testing/util/UtilTests.java b/testing-util/testsrc/org/aspectj/testing/util/UtilTests.java new file mode 100644 index 000000000..a32bd424c --- /dev/null +++ b/testing-util/testsrc/org/aspectj/testing/util/UtilTests.java @@ -0,0 +1,32 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +package org.aspectj.testing.util; + +import junit.framework.*; + +public class UtilTests extends TestCase { + + public static Test suite() { + TestSuite suite = new TestSuite(UtilTests.class.getName()); + // for now, do not include SuiteTest because it would take 15 minutes + //$JUnit-BEGIN$ + suite.addTestSuite(TestUtilTest.class); + //$JUnit-END$ + return suite; + } + + public UtilTests(String name) { super(name); } + +} diff --git a/testing/.classpath b/testing/.classpath new file mode 100644 index 000000000..f24197aa8 --- /dev/null +++ b/testing/.classpath @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/testing/.project b/testing/.project new file mode 100644 index 000000000..12bc71513 --- /dev/null +++ b/testing/.project @@ -0,0 +1,21 @@ + + + testing + + + bridge + testing-client + testing-util + util + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/testing/src/.cvsignore b/testing/src/.cvsignore new file mode 100644 index 000000000..a3f0b1b77 --- /dev/null +++ b/testing/src/.cvsignore @@ -0,0 +1 @@ +*.lst diff --git a/testing/src/org/aspectj/internal/tools/ant/taskdefs/Ajctest.java b/testing/src/org/aspectj/internal/tools/ant/taskdefs/Ajctest.java new file mode 100644 index 000000000..ec112d2b5 --- /dev/null +++ b/testing/src/org/aspectj/internal/tools/ant/taskdefs/Ajctest.java @@ -0,0 +1,1864 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +package org.aspectj.internal.tools.ant.taskdefs; + +import java.beans.*; +import java.io.*; +import java.util.*; +import java.util.List; +import java.text.*; +import org.apache.tools.ant.*; +import org.apache.tools.ant.taskdefs.*; +import org.apache.tools.ant.types.*; +import org.aspectj.util.LangUtil; + +import java.awt.*; +import javax.swing.*; +import javax.swing.border.*; +import javax.swing.event.*; +import javax.swing.table.*; +import javax.swing.text.*; + +public class Ajctest extends Task implements PropertyChangeListener { + private static Ajctest CURRENT_AJCTEST; + + // todo shutdown hook assumes one task per VM + public Ajctest() { + super(); + CURRENT_AJCTEST = this; + } + + private static boolean firstTime = true; + + public final PropertyChangeSupport bean = new PropertyChangeSupport(this); + + { + bean.addPropertyChangeListener(this); + } + + public void propertyChange(PropertyChangeEvent evt) { + String name = evt.getPropertyName(); + if ("ajdoc.good".equals(name)) ajdocStats.goods++; + else if ("ajc.good".equals(name)) ajcStats.goods++; + else if ("run.good".equals(name)) runStats.goods++; + if ("ajdoc.fail".equals(name)) ajdocStats.fails++; + else if ("ajc.fail".equals(name)) ajcStats.fails++; + else if ("run.fail".equals(name)) runStats.fails++; + } + + private void fire(String prop, Object oldval, Object newval) { + bean.firePropertyChange(prop, oldval, newval); + } + + private void fire(String prop) { + fire(prop, "dummy-old", "dummy-new"); + } + + private static boolean dumpresults = false; + private static Stats ajdocStats = new Stats(); + private static Stats ajcStats = new Stats(); + private static Stats runStats = new Stats(); + private static Stats errorStats = new Stats(); + private static final String NO_TESTID = "NONE"; + private File workingdir = new File("ajworkingdir"); //XXX + + //fields + private String testId = NO_TESTID; + private List args = new Vector(); + private List testsets = new Vector(); + private Path classpath; + private Path internalclasspath; + private File destdir; + private File dir; + private File errordir; + private File errorfile; + private List testclasses = new Vector(); + private boolean nocompile; + private Ajdoc ajdoc = null; + private boolean noclean; + private boolean noverify; + private List depends = new Vector(); + //end-fields + + public Argfile createArgfile() { + return createTestset().createArgfile(); + } + + public void setNoverify(boolean input) { + if (input != noverify) noverify = input; + } + + public void setOwningTarget(Target target) { + super.setOwningTarget(target); + if (null != target) { + //setTestId(target.getName()); + } + } + + public void setTestId(String str) { + if ((null != str) && (0 < str.trim().length())) { + testId = str; + } + } + + public void setArgs(String str) { + if (str == null || str.length() < 1) return; + StringTokenizer tok = new StringTokenizer(str, ",", false); + while (tok.hasMoreTokens()) { + String name = tok.nextToken().trim(); + if (0 < name.length()) { + parse(name.startsWith("J") ? createJarg() : createArg(), name); + } + } + } + + private void parse(Argument arg, String name) { + int itilde = name.lastIndexOf("~"); + if (itilde != -1) { + name = name.substring(0, itilde) + name.substring(itilde+1); + } + int ieq = name.lastIndexOf("="); + int icolon = name.lastIndexOf(":"); + int ileft = name.lastIndexOf("["); + int iright = name.lastIndexOf("]"); + boolean always = true; + String rest = ""; + String newName = name; + if (ieq != -1) { + rest = name.substring(ieq+1); + newName = name.substring(0, ieq); + always = true; + } else if (icolon != -1) { + rest = name.substring(icolon+1); + newName = name.substring(0, icolon); + always = false; + } else if (ileft != -1) { + newName = name.substring(0, ileft); + always = true; + } + String values = ileft == -1 ? rest : + name.substring(ileft+1, iright > ileft ? iright : rest.length()-1); + String value = null; + if (itilde != -1) { + String prop = project.getUserProperty(values); + if (prop == null) { + prop = project.getProperty(values); + } + if (prop != null) { + value = prop; + } + } + if (value != null) { + arg.setValue(value); + } else { + arg.setValues(values); + } + arg.setName(newName); + arg.setAlways(always); + } + + public Argument createJarg() { + Argument arg = new Argument(true); + args.add(arg); + return arg; + } + + + public Argument createArg() { + Argument arg = new Argument(false); + args.add(arg); + return arg; + } + + public void setClasspath(Path path) { + if (classpath == null) { + classpath = path; + } else { + classpath.append(path); + } + } + + public Path createClasspath() { + if (classpath == null) { + classpath = new Path(project); + } + return classpath.createPath(); + } + + public void setClasspathRef(Reference r) { + createClasspath().setRefid(r); + } + + public void setInternalclasspath(Path path) { + if (internalclasspath == null) { + internalclasspath = path; + } else { + internalclasspath.append(path); + } + } + + public Path createInternalclasspath() { + if (internalclasspath == null) { + internalclasspath = new Path(project); + } + return internalclasspath.createPath(); + } + + public void setInternalclasspathRef(Reference r) { + createInternalclasspath().setRefid(r); + } + + public void setDestdir(String destdir) { + this.destdir = project.resolveFile(destdir); + } + + public void setDir(File dir) { + this.dir = dir; + } + + public void setErrordir(File dir) { + this.errordir = errordir; + } + + public void setErrorfile(File errorfile) { + this.errorfile = errorfile; + } + + public Run createJava() { + Run testclass = new Run(project); + testclasses.add(testclass); + return testclass; + } + + public void setClasses(String str) { + for (StringTokenizer t = new StringTokenizer(str, ", ", false); + t.hasMoreTokens();) { + createJava().setClassname(t.nextToken().trim()); + } + } + + public void setTestclass(String testclass) { + createJava().setClassname(testclass); + } + + public void setAjdoc(boolean b) { + if (b && ajdoc == null) { + createAjdoc(); + } else if (!b) { + ajdoc = null; + } + } + + public void setAjdocargs(String str) { + createAjdoc(); + for (StringTokenizer t = new StringTokenizer(str, ", ", false); + t.hasMoreTokens();) { + ajdoc.createArg().setValue(t.nextToken().trim()); + } + } + + public void addAjdoc(Ajdoc ajdoc) { + this.ajdoc = ajdoc; + } + + public Ajdoc createAjdoc() { + return ajdoc = new Ajdoc(); + } + + public static class Argument { + private String name; + private List values = new Vector(); + private boolean always = true; + final boolean isj; + public Argument(boolean isj) { + this.isj = isj; + } + public void setName(String str) { + this.name = str.startsWith("-") ? str : + ("-" + (str.startsWith("J") ? str.substring(1) : str)); + } + public void setValues(String str) { + values = new Vector(); + StringTokenizer tok = new StringTokenizer(str, ", ", false); + while (tok.hasMoreTokens()) { + values.add(tok.nextToken().trim()); + } + } + public void setValue(String value) { + (values = new Vector()).add(value); + } + public void setAlways(boolean always) { + this.always = always; + } + public String toString() { return name + ":" + values; } + } + + public void setNocompile(boolean nocompile) { + this.nocompile = nocompile; + } + + private static class Stats { + int goods = 0; + int fails = 0; + } + + private static class Arg { + final String name; + final String value; + final boolean isj; + Arg(String name, String value, boolean isj) { + this.name = name; + this.value = value; + this.isj = isj; + } + public String toString() { + return name + (!"".equals(value) ? " " + value : ""); + } + } + + public Testset createTestset() { + Testset testset = new Testset(); + testsets.add(testset); + return testset; + } + + public void setNoclean(boolean noclean) { + this.noclean = noclean; + } + + public void setDepends(String depends) { + for (StringTokenizer t = new StringTokenizer(depends, ", ", false); + t.hasMoreTokens();) { + this.depends.add(t.nextToken().trim()); + } + } + //end-methods + + public static class Argfile { + private String name; + public void setName(String name) { this.name = name; } + } + + public class Ajdoc { + private Commandline cmd = new Commandline(); + public Commandline.Argument createArg() { return cmd.createArgument(); } + public Commandline getCommandline() { return cmd; } + public String toString() { return cmd + ""; } + } + + public class Testset extends FileSet { + private List argfileNames = new Vector(); + public List argfiles; + public List files; + public List args = new Vector(); + public String classname; + private boolean havecludes = false; + private List testclasses = new Vector(); + private Path classpath; + private Path internalclasspath; + private Ajdoc ajdoc = null; + private boolean fork = false; + private boolean noclean; + private List depends = new Vector(); + public String toString() { + String str = ""; + if (files.size() > 0) { + str += "files:" + "\n"; + for (Iterator i = files.iterator(); i.hasNext();) { + str += "\t" + i.next() + "\n"; + } + } + if (argfiles.size() > 0) { + str += "argfiles:" + "\n"; + for (Iterator i = argfiles.iterator(); i.hasNext();) { + str += "\t" + i.next() + "\n"; + } + } + if (args.size() > 0) { + str += "args:" + "\n"; + for (Iterator i = args.iterator(); i.hasNext();) { + str += "\t" + i.next() + "\n"; + } + } + if (testclasses.size() > 0) { + str += "classes:" + "\n"; + for (Iterator i = testclasses.iterator(); i.hasNext();) { + str += "\t" + i.next() + "\n"; + } + } + return str; + } + public void setIncludes(String includes) { + super.setIncludes(includes); + havecludes = true; + } + public void setExcludes(String excludes) { + super.setExcludes(excludes); + havecludes = true; + } + public void setIncludesfile(File includesfile) { + super.setIncludesfile(includesfile); + havecludes = true; + } + public void setExcludesfile(File excludesfile) { + super.setExcludesfile(excludesfile); + havecludes = true; + } + + public void setArgfile(String name) { + createArgfile().setName(name); + } + + public void setArgfiles(String str) { + StringTokenizer tok = new StringTokenizer(str, ", ", false); + while (tok.hasMoreTokens()) { + createArgfile().setName(tok.nextToken().trim()); + } + + } + public Argfile createArgfile() { + Argfile argfile = new Argfile(); + argfileNames.add(argfile); + return argfile; + } + public Run createJava() { + // See crashing note + //Run testclass = new Run(); + Run testclass = new Run(project); + this.testclasses.add(testclass); + return testclass; + } + public void addJava(Run run) { + this.testclasses.add(run); + } + public void setJava(String str) { + StringTokenizer t = new StringTokenizer(str, " "); + Run run = createJava(); + run.setClassname(t.nextToken().trim()); + while (t.hasMoreTokens()) { + run.createArg().setValue(t.nextToken().trim()); + } + } + public void setTestclass(String testclass) { + createJava().setClassname(testclass); + } + + public void setClasses(String str) { + for (StringTokenizer t = new StringTokenizer(str, ", ", false); + t.hasMoreTokens();) { + createJava().setClassname(t.nextToken().trim()); + } + } + public void setClasspath(Path path) { + if (classpath == null) { + classpath = path; + } else { + classpath.append(path); + } + } + + public Path createClasspath() { + if (classpath == null) { + classpath = new Path(project); + } + return classpath.createPath(); + } + + public void setClasspathRef(Reference r) { + createClasspath().setRefid(r); + } + public void setInternalclasspath(Path path) { + if (internalclasspath == null) { + internalclasspath = path; + } else { + internalclasspath.append(path); + } + } + + public Path createInternalclasspath() { + if (internalclasspath == null) { + internalclasspath = new Path(project); + } + return internalclasspath.createPath(); + } + + public void setInternalclasspathRef(Reference r) { + createInternalclasspath().setRefid(r); + } + + public void setAjdoc(boolean b) { + if (b && ajdoc == null) { + createAjdoc(); + } else if (!b) { + this.ajdoc = null; + } + } + public Ajdoc getAjdoc() { return this.ajdoc; } + public void setAjdocargs(String str) { + createAjdoc(); + for (StringTokenizer t = new StringTokenizer(str, ", ", false); + t.hasMoreTokens();) { + this.ajdoc.createArg().setValue(t.nextToken().trim()); + } + } + public void addAjdoc(Ajdoc ajdoc) { + this.ajdoc = ajdoc; + } + public Ajdoc createAjdoc() { + return this.ajdoc = new Ajdoc(); + } + public void setFork(boolean fork) { + this.fork = fork; + } + public void setNoclean(boolean noclean) { + this.noclean = noclean; + } + public void setDepends(String depends) { + for (StringTokenizer t = new StringTokenizer(depends, ", ", false); + t.hasMoreTokens();) { + this.depends.add(t.nextToken().trim()); + } + } + //end-testset-methods + + public void resolve() throws BuildException { + if (dir != null) this.setDir(dir); + File src = getDir(project); + argfiles = new Vector(); + files = new Vector(); + for(Iterator iter = argfileNames.iterator(); iter.hasNext();) { + String name = ((Argfile)iter.next()).name; + File argfile = new File(src, name); + if (check(argfile, name, location)) argfiles.add(argfile); + } + if (havecludes || argfiles.size() <= 0) { + String[] filenames = + getDirectoryScanner(project).getIncludedFiles(); + for (int j = 0; j < filenames.length; j++) { + String name = filenames[j]; + if (name.endsWith(".java")) { + File file = new File(src, name); + if (check(file, name, location)) files.add(file); + } + } + } + for (Iterator i = Ajctest.this.testclasses.iterator(); + i.hasNext();) { + this.testclasses.add((Run)i.next()); + } + if (this.classpath == null) { + setClasspath(Ajctest.this.classpath); + } + if (this.internalclasspath == null) { + setInternalclasspath(Ajctest.this.internalclasspath); + } + if (this.ajdoc == null) { + this.ajdoc = Ajctest.this.ajdoc; + } + if (this.fork) { + for (Iterator i = this.testclasses.iterator(); i.hasNext();) { + ((Run)i.next()).setFork(fork); + } + } + if (!this.noclean) { + this.noclean = Ajctest.this.noclean; + } + this.depends.addAll(Ajctest.this.depends); + } + private boolean check(File file, String name, Location loc) + throws BuildException { + loc = loc != null ? loc : location; + if (file == null) { + throw new BuildException + ("file " + name + " is null!", loc); + } + if (!file.exists()) { + throw new BuildException + ("file " + file + " with name " + name + + " doesn't exist!", loc); + } + return true; + } + public void setArgs(String str) { + if (str == null || str.length() < 1) return; + StringTokenizer tok = new StringTokenizer(str, ",", false); + while (tok.hasMoreTokens()) { + String name = tok.nextToken().trim(); + parse(name.startsWith("J") ? createJarg() : createArg(), name); + } + } + + public Argument createJarg() { + Argument arg = new Argument(true); + args.add(arg); + return arg; + } + + public Argument createArg() { + Argument arg = new Argument(false); + args.add(arg); + return arg; + } + } + + private void prepare() throws BuildException { + + } + + private void finish() throws BuildException { + if (errors.size() > 0) { + log(""); + log("There " + w(errors) + " " + errors.size() + " errors:"); + for (int i = 0; i < errors.size(); i++) { + log(" ", (Failure)errors.get(i), i); + } + } + allErrors.addAll(errors); + } + + private void log(String space, Failure failure, int num) { + String number = "[" + num + "] "; + log(enough(number, 60, '-')); + for (int i = number.length()-1; i > 0; i--) space += " "; + log(space, failure.testset.files, "files:"); + log(space, failure.testset.argfiles, "argfiles:"); + log(space, failure.args, "args:"); + log(space + "msgs:" + failure.msgs); + } + + + private String enough(String str, int size, char filler) { + while (str.length() < size) str += filler; + return str; + } + + + private void log(String space, List list, String title) { + if (list == null || list.size() < 1) return; + log(space + title); + for (Iterator i = list.iterator(); i.hasNext();) { + log(space + " " + i.next()); + } + } + + private void execute(Testset testset, List args) throws BuildException { + if (testset.files.size() > 0) { + log("\tfiles:"); + for (Iterator i = testset.files.iterator(); + i.hasNext();) { + log("\t " + i.next()); + } + } + if (testset.argfiles.size() > 0) { + log("\targfiles:"); + for (Iterator i = testset.argfiles.iterator(); + i.hasNext();) { + log("\t " + i.next()); + } + } + if (args.size() > 0) { + log("\targs:"); + for (Iterator i = args.iterator(); + i.hasNext();) { + log("\t " + i.next()); + } + } + if (testset.testclasses.size() > 0) { + log("\tclasses:"); + for (Iterator i = testset.testclasses.iterator(); + i.hasNext();) { + log("\t " + i.next()); + } + } + if (!testset.noclean && + (!isSet("noclean") && !isSet("nocompile"))) { + delete(destdir); + make(destdir); + } + delete(workingdir); + make(workingdir); + for (Iterator i = testset.depends.iterator(); i.hasNext();) { + String target = i.next()+""; + // todo: capture failures here? + project.executeTarget(target); + } + int exit; + if (!isSet("nodoc") && testset.ajdoc != null) { + log("\tdoc... " + testset.ajdoc); + AjdocWrapper ajdoc = new AjdocWrapper(testset, args); + if ((exit = ajdoc.run()) != 0) { + post(testset, args, ajdoc.msgs, exit, "ajdoc"); + } else { + fire("ajdoc.good"); + } + fire("ajdoc.done"); + log("\tdone with ajdoc."); + } + boolean goodCompile = true; + if (!isSet("nocompile") && !nocompile) { + log("\tcompile" + + (testset.noclean ? "(boostrapped)" : "") + "..."); + //AjcWrapper ajc = new AjcWrapper(testset, args); + JavaCommandWrapper ajc; + // XXX dependency on Ant property ajctest.compiler + final String compiler = getAntProperty("ajctest.compiler"); + if ("eclipse".equals(compiler) || "eajc".equals(compiler)) { + ajc = new EAjcWrapper(testset, args); + } else if ((null == compiler) || "ajc".equals(compiler)) { + ajc = new AjcWrapper(testset, args); + } else if ("javac".equals(compiler)) { + throw new Error("javac not supported"); + //ajc = new JavacWrapper(testset, args); + } else { + throw new Error("unknown compiler: " + compiler); + } + + System.out.println("using compiler: " + ajc); + try { + if ((exit = ajc.run()) != 0) { + post(testset, args, ajc.msgs, exit, "ajc"); + goodCompile = false; + } else { + fire("ajc.good"); + } + fire("ajc.done"); + } catch (Throwable ___) { + post(testset, args, ___+"", -1, "ajc"); + goodCompile = false; + } + } + if (!goodCompile) { + post(testset, new Vector(), + "couldn't run classes " + testset.testclasses + + "due to failed compile", + -1, "run"); + + } else if (!isSet("norun")) { + for (Iterator i = testset.testclasses.iterator(); + i.hasNext();) { + Run testclass = (Run)i.next(); + log("\ttest..." + testclass.classname()); + if (null != destdir) { + testclass.setClassesDir(destdir.getAbsolutePath()); + } + if ((exit = testclass.executeJava()) != 0) { + post(testset, new Vector(), testclass.msgs, exit, "run"); + } else { + fire("run.good"); + } + fire("run.done"); + } + } + log(""); + } + + public void execute() throws BuildException { + gui(this); + dumpresults = isSet("dumpresults"); + prepare(); + log(testsets.size() + " testset" + s(testsets), + Project.MSG_VERBOSE); + Map testsetToArgcombo = new HashMap(); + List argcombos = new Vector(); + for (Iterator iter = testsets.iterator(); iter.hasNext();) { + Testset testset = (Testset)iter.next(); + testset.resolve(); + List bothargs = new Vector(args); + bothargs.addAll(testset.args); + List argcombo = argcombo(bothargs); + argcombos.add(new Integer(argcombo.size())); + testsetToArgcombo.put(testset, argcombo); + } + while (!testsetToArgcombo.isEmpty()) { + int _ = 1; + for (Iterator iter = testsets.iterator(); iter.hasNext(); _++) { + Testset testset = (Testset)iter.next(); + List argcombo = (List)testsetToArgcombo.get(testset); + if (argcombo.size() == 0) { + testsetToArgcombo.remove(testset); + continue; + } + List args = (List)argcombo.remove(0); + final String startStr = "Testset " + _ + " of " + testsets.size(); + String str = startStr + " / Combo " + _ + " of " + argcombos.size(); + log("---------- " + str + " ----------"); + execute(testset, args); + } + } + +// for (Iterator iter = testsets.iterator(); iter.hasNext(); _++) { +// Testset testset = (Testset)iter.next(); +// testset.resolve(); +// List bothargs = new Vector(args); +// bothargs.addAll(testset.args); +// int __ = 1; +// List argcombo = argcombo(bothargs); +// log(argcombo.size() + " combination" + s(argcombo), +// Project.MSG_VERBOSE); +// final String startStr = "Testset " + _ + " of " + testsets.size(); +// for (Iterator comboiter = argcombo.iterator(); +// comboiter.hasNext(); __++) { +// List args = (List)comboiter.next(); +// execute(testset, args); +// log(""); +// } +// } + finish(); + } + + private void delete(File dir) throws BuildException { + Delete delete = (Delete)project.createTask("delete"); + delete.setDir(dir); + delete.execute(); + } + + private void make(File dir) throws BuildException { + Mkdir mkdir = (Mkdir)project.createTask("mkdir"); + mkdir.setDir(dir); + mkdir.execute(); + } + + private String getAntProperty(String name) { + String uprop = project.getUserProperty(name); + if (null == uprop) { + uprop = project.getProperty(name); + } + return uprop; + } + + private boolean isSet(String name) { + String uprop = project.getUserProperty(name); + if (uprop == null || + "no".equals(uprop) || + "false".equals(uprop)) return false; + String prop = project.getProperty(name); + if (prop == null || + "no".equals(prop) || + "false".equals(prop)) return false; + return true; + } + + /** + * Interpose Wrapper class to catch and report exceptions + * by setting a positive value for System.exit(). + * (In some cases it seems that Exceptions are not being reported + * as errors in the tests.) + * This forces the VM to fork. A forked VM is required for + * two reasons: + * (1) The wrapper class may have been defined by a different + * class loader than the target class, so it would not be able + * to load the target class; + *

    + * (2) Since the wrapper class is generic, we have to pass in + * the name of the target class. I choose to do this using + * VM properties rather than hacking up the arguments. + *

    todo: relies on name/value of property "taskdef.jar" + * to add jar with wrapper to invoking classpath. + *

    + * It is beneficial for another reason: + * (3) The wrapper class can be asked to test-load all classes + * in a classes dir, by setting a VM property. This class + * sets up the property if the value is defined using + * setClassesDir(String) + *

    todo: if more tunnelling, generalize and parse. + */ + public class RunWrapper extends Java { + public final Class LINK_WRAPPER_CLASS = MainWrapper.class; + /** tracked in MainWrapper.PROP_NAME */ // todo: since reflective, avoid direct + public final String PROP_NAME = "MainWrapper.classname"; + /** tracked in MainWrapper.CLASSDIR_NAME */ + public final String CLASSDIR_NAME = "MainWrapper.classdir"; + public final String WRAPPER_CLASS + = "org.aspectj.internal.tools.ant.taskdefs.MainWrapper"; + private String classname; + protected String classesDir; + /** capture classname here, replace with WRAPPER_CLASS */ + public void setClassname(String classname) { + super.setClassname(WRAPPER_CLASS); + this.classname = classname; + } + + /** + * Setup the requirements for the wrapper class: + *

  • fork to get classpath and VM properties right
  • + *
  • set VM property
  • + *
  • add ${ajctest.wrapper.jar} (with wrapper class) to the classpath
  • + */ + private void setup() { + setFork(true); + Commandline.Argument cname = createJvmarg(); + cname.setValue("-D"+PROP_NAME+"="+classname); + if (!noverify) { + cname = createJvmarg(); + cname.setValue("-Xfuture"); // todo: 1.2 or later.. + } + if (null != classesDir) { + cname = createJvmarg(); + cname.setValue("-D"+CLASSDIR_NAME+"="+classesDir); + } + // todo dependence on name/setting of ajctest.wrapper.jar + String value = project.getProperty("ajctest.wrapper.jar"); + if (null != value) { + Path wrapperPath = new Path(project, value); + RunWrapper.this.createClasspath().append(wrapperPath); + } + } + + /** do setup, then super.execute() */ + public int executeJava() { + setup(); + int result = super.executeJava(); + // snarf - also load all classes? + return result; + } + + /** set directory to scan for classes */ + public void setClassesDir(String dir) { + classesDir = dir; + } + } + + public class Run extends RunWrapper { + //public class Run extends Java + private Path bootclasspath; + public void setBootbootclasspath(Path path) { + if (bootclasspath == null) { + bootclasspath = path; + } else { + bootclasspath.append(path); + } + } + public Path createBootbootclasspath() { + if (bootclasspath == null) bootclasspath = new Path(this.project); + return bootclasspath.createPath(); + } + public void setBootbootclasspathRef(Reference r) { + createBootbootclasspath().setRefid(r); + } + private Path bootclasspatha; + public void setBootbootclasspatha(Path path) { + if (bootclasspatha == null) { + bootclasspatha = path; + } else { + bootclasspatha.append(path); + } + } + public Path createBootbootclasspatha() { + if (bootclasspatha == null) bootclasspatha = new Path(this.project); + return bootclasspatha.createPath(); + } + public void setBootbootclasspathaRef(Reference r) { + createBootbootclasspatha().setRefid(r); + } + private Path bootclasspathp; + public void setBootbootclasspathp(Path path) { + if (bootclasspathp == null) { + bootclasspathp = path; + } else { + bootclasspathp.append(path); + } + } + public Path createBootbootclasspathp() { + if (bootclasspathp == null) bootclasspathp = new Path(this.project); + return bootclasspathp.createPath(); + } + public void setBootbootclasspathpRef(Reference r) { + createBootbootclasspathp().setRefid(r); + } + public Run(Project project) { + super(); + //this.project = Ajctest.this.project; + this.setTaskName("ajcjava"); + this.project = project; + } + public String msgs = ""; + public int executeJava() { + Path cp = Ajctest.this.classpath != null ? Ajctest.this.classpath : + new Path(this.project, destdir.getAbsolutePath()); + cp.append(Path.systemClasspath); + this.setClasspath(cp); + if (bootclasspath != null) { + setFork(true); + createJvmarg().setValue("-Xbootclasspath:" + bootclasspath); + } + if (bootclasspatha != null) { + setFork(true); + createJvmarg().setValue("-Xbootclasspath/a:" + bootclasspatha); + } + if (bootclasspathp != null) { + setFork(true); + createJvmarg().setValue("-Xbootclasspath/p:" + bootclasspathp); + } + int exit = -1; + // todo: add timeout feature todo: this or below? + try { + exit = super.executeJava(); + } catch (Throwable t) { + StringWriter sw = new StringWriter(); + PrintWriter out = new PrintWriter(sw); + t.printStackTrace(out); + msgs = sw.toString(); + out.close(); + // todo: return exit code + } + return exit; + } + public String _classname; + public String classname() { return _classname; } + public void setClassname(String classname) { + super.setClassname(_classname = classname); + } + public String toString() { return _classname; } + } + // class Run + // todo: need to run in a wrapper which report non-zero int on exception + // todo: unused method? see executeJava above. + private int java(String classname, Collection args) throws BuildException { + Java java = (Java)project.createTask("java"); + java.setClassname(classname); + for (Iterator i = args.iterator(); i.hasNext();) { + Object o = i.next(); + Commandline.Argument arg = java.createArg(); + if (o instanceof File) { + arg.setFile((File)o); + } else if (o instanceof Path) { + arg.setPath((Path)o); + } else { + arg.setValue(o+""); + } + } + return java.executeJava(); + } + + private static List allErrors = new Vector(); + private List errors = new Vector(); + + private void post(Testset testset, List args, + String msgs, int exit, String type) { + errors.add(new Failure(testset, args, msgs, exit, type, testId)); + fire(type + ".fail"); + } + + private static long startTime; + private static long stopTime; + + private static String date(long time) { + return DateFormat.getDateTimeInstance + (DateFormat.FULL, DateFormat.FULL). + format(new Date(time)); + } + + static { + final PrintStream err = System.err; + startTime = System.currentTimeMillis(); + Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { + private String ms(long start, long stop) { + long rest = Math.abs(stop-start) / 1000; + long days = rest / 86400; + long hours = (rest -= days*86400) / 3600; + long mins = (rest -= hours*3600) / 60; + long secs = (rest -= mins*60); + boolean req = false; + String str = ""; + if (req || days > 0) { + req = true; + str += days + " day" + (days != 1 ? "s" : "") + " "; + } + if (req || hours > 0) { + req = true; + str += hours + " hour" + (hours != 1 ? "s" : "") + " "; + } + if (req || mins > 0) { + req = true; + str += mins + " minute" + (mins != 1 ? "s" : "") + " "; + } + str += secs + " second" + (secs != 1 ? "s" : "") + " "; + return str; + } + + public void run() { + Ajctest current = CURRENT_AJCTEST; + String oneLine = "warning: oneLine not set."; + String multiLine = "warning: multiLine not set."; + + // setup oneLine + if (null == current) { + oneLine = "\nRESULT=\"ERROR\" null ACJTEST"; + } else { + StringBuffer sb = new StringBuffer("\n"); + int errs = current.allErrors.size(); + int allFails = errs + + current.ajdocStats.fails + + current.ajcStats.fails + + current.runStats.fails; + if (1 > allFails) { + sb.append("RESULT=\"PASS\"\terrors=\""); + } else { + sb.append("RESULT=\"FAIL\"\terrors=\""); + } + sb.append(""+errs); + sb.append("\"\tajdoc.pass=\""); + sb.append(""+current.ajdocStats.goods); + sb.append("\"\tajdoc.fail=\""); + sb.append(""+current.ajdocStats.fails); + sb.append("\"\tajc.pass=\""); + sb.append(""+current.ajcStats.goods); + sb.append("\"\tajc.fail=\""); + sb.append(""+current.ajcStats.fails); + sb.append("\"\trun.pass=\""); + sb.append(""+current.runStats.goods); + sb.append("\"\trun.fail=\""); + sb.append(""+current.runStats.fails); + sb.append("\"\ttestId=\""); + sb.append(current.testId); + sb.append("\"\tproject=\""); + Project p = current.getProject(); + if (null != p) sb.append(p.getName()); + sb.append("\"\tfile=\""); + sb.append(""+current.getLocation()); + sb.append("\""); + oneLine = sb.toString(); + } + + // setup multiLine + { + stopTime = System.currentTimeMillis(); + String str = ""; + str += "\n"; + str += + "===================================" + + "===================================" + "\n"; + str += "Test started : " + date(startTime) + "\n"; + str += "Test ended : " + date(stopTime) + "\n"; + str += "Total time : " + ms(startTime, stopTime) + "\n"; + str += + "------------------------------" + + " Summary " + + "------------------------------" + "\n"; + str += "Task\tPassed\tFailed" + "\n"; + Object[] os = new Object[] { + "ajdoc", ajdocStats.goods+"", ajdocStats.fails+"", + "ajc", ajcStats.goods +"", ajcStats.fails +"", + "run", runStats.goods +"", runStats.fails +"", + }; + for (int i = 0; i < os.length; i += 3) { + str += os[i] + "\t" + os[i+1] + "\t" + os[i+2] + "\n"; + } + if (allErrors.size() > 0) { + str += "" + "\n"; + str += + "There " + w(allErrors) + " " + + allErrors.size() + " error" + + s(allErrors) + ":" + "\n"; + for (int i = 0; i < allErrors.size(); i++) { + Failure failure = (Failure)allErrors.get(i); + str += + "---------- Error " + i + " [" + + failure.testId + "]" + + " ------------------------------" + "\n"; + str += " " + failure + "\n\n"; + } + } else { + str += "No errors." + "\n"; + } + str += "--------------------------" + + " End of Summary " + + "---------------------------" + "\n"; + multiLine = str; + } + + // print both multiLine and oneLine + err.println(multiLine); + err.println(oneLine); + if (dumpresults && (allErrors.size() + + ajdocStats.fails + + ajcStats.fails + + runStats.fails) > 0) { + String date = date(System.currentTimeMillis()); + String filename = "ajc-errors"; + for (StringTokenizer t = new StringTokenizer(date, ",: "); + t.hasMoreTokens();) { + filename += "-" + t.nextToken().trim(); + } + filename += ".txt"; + PrintWriter out = null; + File file = new File(filename); + System.err.println("dumping results to " + file); + try { + out = new PrintWriter(new FileWriter(file)); + out.println(multiLine); + out.println(oneLine); + System.err.println("dumped results to " + file); + } catch (IOException ioe) { + if (out != null) out.close(); + } + } + } + })); + } + + private static String w(List list) { return a(list, "were", "was"); } + private static String s(List list) { return a(list, "s", ""); } + + private static String a(List list, String some, String one) { + return list == null || list.size() != 1 ? some : one; + } + + static class Failure { + public final Testset testset; + public final List args; + public final String msgs; + public final int exit; + public final String type; + public final long time; + public final String testId; + public Failure(Testset testset, List args, + String msgs, int exit, String type, + String testId) { + this.testset = testset; + this.args = args; + this.msgs = msgs; + this.exit = exit; + this.type = type; + this.time = System.currentTimeMillis(); + this.testId = testId; + } + public String toString() { + String str = "testId:" + testId+ "\n"; + str += "type:" + type + "\n"; + str += testset + "\n"; + if (args.size() > 0) { + str += " args: " + args + "\n";; + } + str += " msgs:" + msgs + "\n"; + str += " exit:" + exit; + return str; + } + } + + private List argcombo(List arguments) { + List combos = new Vector(); + List always = new Vector(); + for (Iterator iter = arguments.iterator(); iter.hasNext();) { + Argument arg = (Argument)iter.next(); + if (arg.values.size() == 0) arg.values.add(""); + if (!arg.always && !arg.values.contains(null)) arg.values.add(null); + if (arg.values.size() > 0) { + combos.add(arg); + } else if (arg.always) { + always.add(new Arg(arg.name, arg.values.get(0)+"", arg.isj)); + } + } + List argcombo = combinations(combos); + for (Iterator iter = always.iterator(); iter.hasNext();) { + Arg arg = (Arg)iter.next(); + for (Iterator comboiter = argcombo.iterator(); comboiter.hasNext();) { + ((List)comboiter.next()).add(arg); + } + } + return argcombo; + } + + private abstract class ExecWrapper { + public String msgs; + public int run() { + return run(createCommandline()); + } + protected abstract Commandline createCommandline(); + protected final int run(Commandline cmd) { + Process process = null; + int exit = Integer.MIN_VALUE; + final StringBuffer buf = new StringBuffer(); + Thread errPumper = null; + Thread outPumper = null; + try { + log("\tcalling " + cmd, Project.MSG_VERBOSE); + process = Runtime.getRuntime().exec(cmd.getCommandline()); + OutputStream os = new OutputStream() { + StringBuffer sb = new StringBuffer(); + public void write(int b) throws IOException { + final char c = (char)b; + buf.append(c); + if (c != '\n') { + sb.append(c); + } else { + System.err.println(sb.toString()); + sb = new StringBuffer(); + } + } + }; + OutputStream los = new LogOutputStream(Ajctest.this, + Project.MSG_INFO); + outPumper = new Thread(new StreamPumper(process.getInputStream(), + los)); + errPumper = new Thread(new StreamPumper(process.getErrorStream(), + os)); + outPumper.setDaemon(true); + errPumper.setDaemon(true); + outPumper.start(); + errPumper.start(); + process.waitFor(); + } catch (Exception e) { + e.printStackTrace(); + //throw e; + } finally { + try { + if (outPumper != null) outPumper.join(); + if (errPumper != null) errPumper.join(); + } catch (InterruptedException ie) { + } finally { + outPumper = null; + errPumper = null; + } + exit = process.exitValue(); + msgs = buf.toString(); + if (exit != 0) { + log("Test failed with exit value: " + exit); + } else { + log("Success!", Project.MSG_VERBOSE); + } + if (process != null) process.destroy(); + process = null; + System.err.flush(); + System.out.flush(); + } + return exit; + } + } + + private class AjcWrapper extends JavaCommandWrapper { + public AjcWrapper(Testset testset, List args) { + super(testset, args, false); + if (testset.noclean) { + setExtraclasspath(new Path(project, + destdir.getAbsolutePath())); + } + } + String getMainClassName() { + return "org.aspectj.tools.ajc.Main"; + } + } + + private class EAjcWrapper extends JavaCommandWrapper { + public EAjcWrapper(Testset testset, List args) { + super(testset, args, false); + if (testset.noclean) { + setExtraclasspath(new Path(project, + destdir.getAbsolutePath())); + } + } + String getMainClassName() { + return "org.aspectj.ajdt.ajc.Main"; + } + } + + static List ajdocArgs(List args) { + List newargs = new Vector(); + for (Iterator i = args.iterator(); i.hasNext();) { + String arg = i.next() + ""; + if (arg.startsWith("-X")) { + newargs.add(arg); + } else if (arg.equals("-public") || + arg.equals("-package") || + arg.equals("-protected") || + arg.equals("-private")) { + newargs.add(arg); + } else if (arg.equals("-d") || + arg.equals("-classpath") || + arg.equals("-cp") || + arg.equals("-sourcepath") || + arg.equals("-bootclasspath") || + arg.equals("-argfile")) { + newargs.add(arg); + newargs.add(i.next()+""); + } else if (arg.startsWith("@")) { + newargs.add(arg); + } + } + return newargs; + } + + private class AjdocWrapper extends JavaCommandWrapper { + public AjdocWrapper(Testset testset, List args) { + super(testset, ajdocArgs(args), true); + String[] cmds = testset.getAjdoc().getCommandline().getCommandline(); + for (int i = 0; i < cmds.length; i++) { + this.args.add(cmds[i]); + } + } + String getMainClassName() { + return "org.aspectj.tools.ajdoc.Main"; + } + } + + private abstract class JavaCommandWrapper extends ExecWrapper { + abstract String getMainClassName(); + protected Testset testset; + protected List args; + protected boolean needsClasspath; + protected Path extraClasspath; + + public JavaCommandWrapper(Testset testset, List args, + boolean needsClasspath) { + this.testset = testset; + this.args = args; + this.needsClasspath = needsClasspath; + this.extraClasspath = testset.internalclasspath; + } + public void setExtraclasspath(Path extraClasspath) { + this.extraClasspath = extraClasspath; + } + + public String toString() { + return LangUtil.unqualifiedClassName(getClass()) + + "(" + getMainClassName() + ")"; + } + + protected Commandline createCommandline() { + Commandline cmd = new Commandline(); + cmd.setExecutable("java"); + cmd.createArgument().setValue("-classpath"); + Path cp = null; + if (extraClasspath != null) { + cp = extraClasspath; + } + if (extraClasspath == null) { + Path aspectjBuildDir = + new Path(project, + project.getProperty("ajctest.pathelement")); + // todo: dependency on ant script variable name ajctest.pathelement + if (cp == null) cp = aspectjBuildDir; + else cp.append(aspectjBuildDir); + } + if (cp == null) { + cp = Path.systemClasspath; + } else { + cp.append(Path.systemClasspath); + } + cmd.createArgument().setPath(cp); + for (Iterator iter = args.iterator(); iter.hasNext();) { + Arg arg = (Arg)iter.next(); + if (arg.isj) { + cmd.createArgument().setValue(arg.name); + if (!arg.value.equals("")) { + cmd.createArgument().setValue(arg.value); + } + } + } + cmd.createArgument().setValue(getMainClassName()); + boolean alreadySetDestDir = false; + boolean alreadySetClasspath = false; + for (Iterator iter = args.iterator(); iter.hasNext();) { + Arg arg = (Arg)iter.next(); + if (!arg.isj) { + cmd.createArgument().setValue(arg.name); + if (arg.name.equals("-d")) { + setDestdir(arg.value+""); + alreadySetDestDir = true; + } + if (arg.name.equals("-classpath")) { + alreadySetClasspath = true; + } + if (!arg.value.equals("")) { + cmd.createArgument().setValue(arg.value); + } + } + } + if (destdir == null) { + setDestdir("."); + } + if (!alreadySetDestDir) { + cmd.createArgument().setValue("-d"); + cmd.createArgument().setFile(destdir); + } + if (!alreadySetClasspath && testset.classpath != null) { + cmd.createArgument().setValue("-classpath"); + cmd.createArgument().setPath(testset.classpath); + } else if (needsClasspath) { + Path _cp = Ajctest.this.classpath != null ? Ajctest.this.classpath : + new Path(project, destdir.getAbsolutePath()); + _cp.append(Path.systemClasspath); + cmd.createArgument().setValue("-classpath"); + cmd.createArgument().setPath(_cp); + } + for (Iterator iter = testset.files.iterator(); iter.hasNext();) { + cmd.createArgument().setFile((File)iter.next()); + } + for (Iterator iter = testset.argfiles.iterator(); iter.hasNext();) { + cmd.createArgument().setValue("-argfile"); + cmd.createArgument().setFile((File)iter.next()); + } + return cmd; + } + } + + /** implement invocation of ajc */ + private void java(Testset testset, List args) throws BuildException { + Java java = (Java)project.createTask("java"); + java.setClassname("org.aspectj.tools.ajc.Main"); + if (classpath != null) { + java.setClasspath(classpath); + } + for (Iterator iter = args.iterator(); iter.hasNext();) { + Arg arg = (Arg)iter.next(); + if (arg.isj) { + java.createJvmarg().setValue(arg.name); + if (!arg.value.equals("")) { + java.createJvmarg().setValue(arg.value); + } + } + } + for (Iterator iter = args.iterator(); iter.hasNext();) { + Arg arg = (Arg)iter.next(); + if (!arg.isj) { + java.createArg().setValue(arg.name); + if (!arg.value.equals("")) { + java.createArg().setValue(arg.value); + } + } + } + for (Iterator iter = testset.files.iterator(); iter.hasNext();) { + java.createArg().setFile((File)iter.next()); + } + for (Iterator iter = testset.argfiles.iterator(); iter.hasNext();) { + java.createArg().setValue("-argfile"); + java.createArg().setFile((File)iter.next()); + } + java.setFork(true); + java.execute(); + } + + private void exec(Testset testset, List args) throws BuildException { + ExecTask exec = (ExecTask)project.createTask("exec"); + exec.setExecutable("java"); + if (classpath != null) { + exec.createArg().setValue("-classpath"); + exec.createArg().setPath(classpath); + } + for (Iterator iter = args.iterator(); iter.hasNext();) { + Arg arg = (Arg)iter.next(); + if (arg.isj) { + exec.createArg().setValue(arg.name); + if (!arg.value.equals("")) { + exec.createArg().setValue(arg.value); + } + } + } + exec.createArg().setValue("org.aspectj.tools.ajc.Main"); + for (Iterator iter = args.iterator(); iter.hasNext();) { + Arg arg = (Arg)iter.next(); + if (!arg.isj) { + exec.createArg().setValue(arg.name); + if (!arg.value.equals("")) { + exec.createArg().setValue(arg.value); + } + } + } + for (Iterator iter = testset.files.iterator(); iter.hasNext();) { + exec.createArg().setFile((File)iter.next()); + } + for (Iterator iter = testset.argfiles.iterator(); iter.hasNext();) { + exec.createArg().setValue("-argfile"); + exec.createArg().setFile((File)iter.next()); + } + exec.execute(); + } + + public void handle(Throwable t) { + log("handling " + t); + if (t != null) t.printStackTrace(); + log("done handling " + t); + } + + private List combinations(List arglist) { + List result = new Vector(); + result.add(new Vector()); + for (Iterator iter = arglist.iterator(); iter.hasNext();) { + Argument arg = (Argument)iter.next(); + int N = result.size(); + for (int i = 0; i < N; i++) { + List to = (List)result.remove(0); + for (Iterator valiter = arg.values.iterator(); valiter.hasNext();) { + List newlist = new Vector(to); + Object val = valiter.next(); + if (val != null) newlist.add(new Arg(arg.name, val+"", arg.isj)); + result.add(newlist); + } + } + } + return result; + } + + /////////////////////// GUI support ////////////////////////////// + private static Gui gui; + + private static void gui(Ajctest ajc) { + if (firstTime && ajc.isSet("gui")) { + JFrame f = new JFrame("AspectJ Test Suite"); + f.getContentPane().add(BorderLayout.CENTER, gui = new Gui()); + f.pack(); + f.setVisible(true); + } + if (gui != null) { + ajc.bean.addPropertyChangeListener(gui); + } + firstTime = false; + } + + private static class Gui extends JPanel implements PropertyChangeListener { + private FailurePanel fail = new FailurePanel(); + private TablePanel table = new TablePanel(); + private StatusPanel status = new StatusPanel(); + public Gui() { + super(new BorderLayout()); + JSplitPane split = new JSplitPane(JSplitPane.VERTICAL_SPLIT); + split.setPreferredSize(new Dimension(500, 300)); + split.add(JSplitPane.BOTTOM, fail); + split.add(JSplitPane.TOP, table); + split.setDividerLocation(200); + add(BorderLayout.CENTER, split); + add(BorderLayout.SOUTH, status); + setPreferredSize(new Dimension(640, 680)); + } + public void propertyChange(PropertyChangeEvent evt) { + String name = evt.getPropertyName(); + if ("ajdoc.good".equals(name)) { + status.ajc.goods.inc(); + } else if ("ajc.good".equals(name)) { + status.ajc.goods.inc(); + } else if ("run.good".equals(name)) { + status.runs.goods.inc(); + } + if ("ajdoc.done".equals(name)) { + status.ajc.total.inc(); + } else if ("ajc.done".equals(name)) { + status.ajc.total.inc(); + } else if ("run.done".equals(name)) { + status.runs.total.inc(); + } + if ("ajdoc.fail".equals(name)) { + status.ajc.fails.inc(); + } else if ("ajc.fail".equals(name)) { + status.ajc.fails.inc(); + } else if ("run.fail".equals(name)) { + status.runs.fails.inc(); + } + } + + private abstract static class TitledPanel extends JPanel { + public TitledPanel(LayoutManager layout, String title) { + super(layout); + setBorder(BorderFactory.createTitledBorder(title)); + } + } + + private static class StatusPanel extends TitledPanel { + StatusInnerPanel ajdoc = new StatusInnerPanel("Ajdoc"); + StatusInnerPanel runs = new StatusInnerPanel("Runs"); + StatusInnerPanel ajc = new StatusInnerPanel("Ajc"); + + public StatusPanel() { + super(new FlowLayout(), "Status"); + add(ajdoc); + add(runs); + add(ajc); + } + + private static class StatusInnerPanel extends TitledPanel { + IntField goods = new IntField(5, Color.green.darker()); + IntField fails = new IntField(5, Color.red.darker()); + IntField total = new IntField(5, Color.blue.darker()); + public StatusInnerPanel(String str) { + super(null, str); + this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); + Object[] os = new Object[] { + "Passed", goods, + "Failed", fails, + "Totals", total, + }; + for (int i = 0; i < os.length; i += 2) { + JPanel p = p(); + p.add(new JLabel(os[i]+":")); + p.add((Component)os[i+1]); + this.add(p); + } + } + + private JPanel p() { + JPanel p = new JPanel(); + p.setLayout(new FlowLayout(FlowLayout.LEFT)); + return p; + } + + private class IntField extends JTextField { + public IntField(int i, Color fg) { + super("0", i); + this.setBackground(StatusInnerPanel.this.getBackground()); + this.setForeground(fg); + this.setEditable(false); + this.setBorder(BorderFactory.createEmptyBorder()); + } + public void add(int i) { + setText((Integer.parseInt(getText().trim())+i)+""); + } + public void inc() { add(1); } + } + } + } + + private class TablePanel extends TitledPanel { + private DefaultTableModel model = new DefaultTableModel(); + private TJable table; + private List failures = new Vector(); + public TablePanel() { + super(new BorderLayout(), "Failures"); + Object[] names = new String[] { + "Task", "Type", "Number", "Time" + }; + for (int i = 0; i < names.length; i++) { + model.addColumn(names[i]); + } + table = new TJable(model, failures); + this.add(new JScrollPane(table), BorderLayout.CENTER); + } + + private class TJable extends JTable { + private List list; + public TJable(TableModel model, List list) { + super(model); + this.list = list; + setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + } + public void valueChanged(ListSelectionEvent e) { + super.valueChanged(e); + if (list == null) return; + int i = (e.getFirstIndex()-e.getLastIndex())/2; + if (list.size() > 0) { + Failure f = (Failure)list.get(i); + fail.setFailure(f); + } + } + } + + public void add(Failure f, String taskname, String type, + int num, long time) { + model.addRow(new Object[]{taskname, type, + new Integer(num), date(time)}); + failures.add(f); + } + } + + private static class FailurePanel extends TitledPanel { + private JTextArea msgs = new JTextArea(10,50); + private InfoPanel info = new InfoPanel(); + + public FailurePanel() { + super(new BorderLayout(), "Failure"); + msgs.setFont(StyleContext.getDefaultStyleContext(). + getFont("SansSerif", Font.PLAIN, 10)); + add(BorderLayout.NORTH, info); + JScrollPane sc = new JScrollPane(msgs); + sc.setBorder(BorderFactory.createTitledBorder("Messages")); + add(BorderLayout.CENTER, sc); + } + + public void setText(String str) { + msgs.setText(str); + } + + public void setFailure(Failure f) { + msgs.setText(f.msgs); + info.setText("Type" , f.type); + info.setText("Args" , f.args); + info.setText("Exit" , f.exit+""); + info.setText("Time" , date(f.time)); + info.setText("Files" , f.testset.files); + info.setText("Classnames" , f.testset.testclasses); + } + + private static class InfoPanel extends JPanel { + Map fields = new HashMap(); + public void setText(String key, Object str) { + ((JTextField)fields.get(key)).setText(str+""); + } + public InfoPanel() { + super(new GridBagLayout()); + LabelFieldGBC gbc = new LabelFieldGBC(); + Object[] os = new Object[] { + "Type", + "Args", + "Exit", + "Time", + "Files", + "Classnames", + }; + for (int i = 0; i < os.length; i++) { + String name = os[i]+""; + JLabel label = new JLabel(name+":"); + JTextField comp = new JTextField(25); + comp.setEditable(false); + comp.setBackground(Color.white); + comp.setBorder(BorderFactory. + createBevelBorder(BevelBorder.LOWERED)); + label.setLabelFor(comp); + fields.put(name, comp); + add(label, gbc.forLabel()); + add(comp, gbc.forField()); + } + add(new JLabel(), gbc.forLastLabel()); + } + } + + private static class LabelFieldGBC extends GridBagConstraints { + public LabelFieldGBC() { + insets = new Insets(1,3,1,3); + gridy = RELATIVE; + gridheight = 1; + gridwidth = 1; + } + public LabelFieldGBC forLabel() { + fill = NONE; + gridx = 0; + anchor = NORTHEAST; + weightx = 0.0; + return this; + } + + public LabelFieldGBC forLastLabel() { + forLabel(); + fill = VERTICAL; + weighty = 1.0; + return this; + } + + public LabelFieldGBC forField() { + fill = HORIZONTAL; + gridx = 1; + anchor = CENTER; + weightx = 1.0; + return this; + } + + public LabelFieldGBC forLastField() { + forField(); + fill = BOTH; + weighty = 1.0; + return this; + } + } + } + } + +} diff --git a/testing/src/org/aspectj/internal/tools/ant/taskdefs/MainWrapper.java b/testing/src/org/aspectj/internal/tools/ant/taskdefs/MainWrapper.java new file mode 100644 index 000000000..656a284f4 --- /dev/null +++ b/testing/src/org/aspectj/internal/tools/ant/taskdefs/MainWrapper.java @@ -0,0 +1,181 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + + + +package org.aspectj.internal.tools.ant.taskdefs; + +import org.aspectj.testing.util.LangUtil; // todo config management here + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.InvocationTargetException; +import java.io.*; + +/** + * Wrapper to invoke class identified by setting VM argument. + * Caller must set a system property "MainWrapper.classname" + * to the fully-qualified name of the target class to invoke, + * and the target class must be resolvable from the defining + * class loader of this class. + * VM argument name is available as PROP_NAME, but + * is set by adding the following to the command line: + * -DMainWrapper.classname="fully.qualified.Classname". + * This returns -1 if unable to load the main method, + * 1 if the invoked method throws an exception, and 0 otherwise. + * TODO: prelminary + */ +public class MainWrapper { + /** MUST set the fully-qualified name of class to invoke using + * a VM property of this name + * tracked in Ajctest.java */ + public static final String PROP_NAME = "MainWrapper.classname"; + /** May set the path to a classes diretory, + * to interpret class names and load classes. + * Tracked in Ajctest.java */ + public static final String CLASSDIR_NAME = "MainWrapper.classdir"; + + /** to disable returning int via System.exit, set to boolean true value (todo: ignored) */ + public static final String SIGNAL_EXCEPTION_NAME = "MainWrapper.signalException"; + + /** to disable returning via System.exit on first Throwable, set to boolean true value (todo: ignored) */ + public static final String FAIL_ON_EXCEPTION_NAME = "MainWrapper.failOnException"; + + /** quit on first exception */ // todo public class controls - yuck + public static boolean FAIL_ON_EXCEPTION = true; + + /** signal number of exceptions with int return value */ + public static boolean SIGNAL_EXCEPTION = true; + + /** redirect messages for exceptions; if null, none printed */ + public static PrintStream OUT_STREAM = System.err; + + /** result accumulated, possibly from multiple threads */ + private static int result; + + /** + * Run target class's main(args), doing a System.exit() with + * a value > 0 for the number of Throwable that + * the target class threw that + * makes it through to a top-level ThreadGroup. (This is + * strictly speaking not correct since applications can live + * after their exceptions stop a thread.) + * Exit with a value < 0 if there were exceptions in loading + * the target class. Messages are printed to OUT_STREAM. + */ + public static void main(String[] args) { + String classname = "no property : " + PROP_NAME; + Method main = null; + // setup: this try block is for loading main method - return -1 if fail + try { + // access classname from jvm arg + classname = System.getProperty(PROP_NAME); + // this will fail if the class is not available from this classloader + Class cl = Class.forName(classname); + final Class[] argTypes = new Class[] {String[].class}; + // will fail if no main method + main = cl.getMethod("main", argTypes); + if (!Modifier.isStatic(main.getModifiers())) { + PrintStream outStream = OUT_STREAM; + if (null != outStream) outStream.println("main is not static"); + result = -1; + } + // if user also request loading of all classes... + String classesDir = System.getProperty(CLASSDIR_NAME); + if ((null != classesDir) && (0 < classesDir.length())) { + MainWrapper.loadAllClasses(new File(classesDir)); + } + } catch (Throwable t) { + if (1 != result) result--; + reportException("setup Throwable invoking class " + classname, t); + } + // run: this try block is for running things - get Throwable from our thread here + if ((null != main) && (0 == result)) { + try { + runInOurThreadGroup(main, args); + } catch (Throwable t) { + if (result > -1) { + result++; + } + reportException("run Throwable invoking class " + classname, t); + } + } + if ((0 != result) && (SIGNAL_EXCEPTION)) { + System.exit(result); + } + } + + static void runInOurThreadGroup(final Method main, final String[] args) { + final String classname = main.getDeclaringClass().getName(); + ThreadGroup ourGroup = new ThreadGroup("MainWrapper ThreadGroup") { + public void uncaughtException(Thread t, Throwable e) { + reportException("uncaughtException invoking " + classname, e); + result++; + if (FAIL_ON_EXCEPTION) { + System.exit((SIGNAL_EXCEPTION ? result : 0)); + } + } + }; + Runnable runner = new Runnable() { + public void run() { + try { + main.invoke(null, new Object[] {args}); + } catch (InvocationTargetException e) { + result = -1; + reportException("InvocationTargetException invoking " + classname, e); + } catch (IllegalAccessException e) { + result = -1; + reportException("IllegalAccessException invoking " + classname, e); + } + } + }; + Thread newMain = new Thread(ourGroup, runner, "pseudo-main"); + newMain.start(); + try { + newMain.join(); + } catch (InterruptedException e) { + result = -1; // todo: InterruptedException might be benign - retry? + reportException("Interrupted while waiting for to join " + newMain, e); + } + } + + /** + * Try to load all classes in a directory. + * @throws Error if any failed + */ + static protected void loadAllClasses(File classesDir) { + if (null != classesDir) { + String[] names = LangUtil.classesIn(classesDir); + StringBuffer err = new StringBuffer(); + LangUtil.loadClasses(names, null, err); + if (0 < err.length()) { + throw new Error("MainWrapper Errors loading classes: " + + err.toString()); + } + } + } + + static void reportException(String context, Throwable t) { + PrintStream outStream = OUT_STREAM; + if (null != outStream) { + while ((null != t) && + (InvocationTargetException.class.isAssignableFrom(t.getClass()))) { + t = ((InvocationTargetException) t).getTargetException(); + } + outStream.println(" context: " + context); + outStream.println(" message: " + t.getMessage()); + t.printStackTrace(outStream); + } + } + +} // MainWrapper diff --git a/testing/src/org/aspectj/testing/gui/resources/gui.properties b/testing/src/org/aspectj/testing/gui/resources/gui.properties new file mode 100644 index 000000000..56a5878ac --- /dev/null +++ b/testing/src/org/aspectj/testing/gui/resources/gui.properties @@ -0,0 +1,53 @@ +gui.actions=quit delete reload open save saveas stop start + + +quit.class=org.aspectj.testing.gui.Gui$QuitAction +quit.icon=Quit24.gif +quit.tooltip=Quit + +delete.class=org.aspectj.testing.gui.Gui$DeleteAction +delete.icon=Delete24.gif +delete.tooltip=Delete Test Cases + +stop.class=org.aspectj.testing.gui.Gui$StopAction +stop.icon=Stop24.gif +stop.tooltip=Stop running test + +reload.class=org.aspectj.testing.gui.Gui$ReloadAction +reload.icon=Reload24.gif +reload.tooltip=Reload current test + +open.class=org.aspectj.testing.gui.Gui$OpenAction +open.icon=Open24.gif +open.tooltip=Open a test + +save.class=org.aspectj.testing.gui.Gui$SaveAction +save.icon=Save24.gif +save.tooltip=Save current test + +saveas.class=org.aspectj.testing.gui.Gui$SaveAsAction +saveas.icon=SaveAs24.gif +saveas.tooltip=Save current test as... + +start.class=org.aspectj.testing.gui.Gui$StartAction +start.icon=Start24.gif +start.tooltip=Start test + +editor.actions=openfile savefile savefileas + +openfile.class=org.aspectj.testing.gui.Gui$OpenAction +openfile.icon=Open24.gif +openfile.tooltip=Open a file + +savefile.class=org.aspectj.testing.gui.Gui$SaveAction +savefile.icon=Save24.gif +savefile.tooltip=Save currect file + +savefileas.class=org.aspectj.testing.gui.Gui$SaveAsAction +savefileas.icon=SaveAs24.gif +savefileas.tooltip=Save current file as... + +gui.width=700 +gui.less.height=100 +gui.top.divider.location=0.70 +gui.center.divider.location=0.60 diff --git a/testing/src/org/aspectj/testing/gui/resources/images/Back16.gif b/testing/src/org/aspectj/testing/gui/resources/images/Back16.gif new file mode 100644 index 0000000000000000000000000000000000000000..f48362d71c2820bfd6d39acf401b88fb604b3c9c GIT binary patch literal 183 zcmZ?wbhEHb6krfwSoELa|NsAI&itP_^9%z6!+`?_fFuMc{$ycfU|?a;0dYZU8JIOa zcHKGmc(351m=F%_&ur7p8)6sqF8mpLYC&$!@1ln?!tW|~zwo=~5XQuy_|M-tzo4=x zGd-h3!N|bCKq0A8A-FV8!8bFxD8IO}xFj{VSVzG#FIi8)F(*eM2&AxBAt<#twWusL aMUR)u%g0T@JxC!ewMfBG&w!VU!5RQ4a6W+m literal 0 HcmV?d00001 diff --git a/testing/src/org/aspectj/testing/gui/resources/images/Delete24.gif b/testing/src/org/aspectj/testing/gui/resources/images/Delete24.gif new file mode 100644 index 0000000000000000000000000000000000000000..d8c8c2d3dbd3fad2c511356ebc8fc8c0ab287604 GIT binary patch literal 93 zcmZ?wbhEHblwgoxSoELa|NsAI&YYP!b7opvnz6Al0|Uc>0|$Vl;!hSv1_m|;9UvR1 wQi6ejIXh$5oqqq9(odp~d)MjmZ`iIF001y@;JOBUy literal 0 HcmV?d00001 diff --git a/testing/src/org/aspectj/testing/gui/resources/images/Forward16.gif b/testing/src/org/aspectj/testing/gui/resources/images/Forward16.gif new file mode 100644 index 0000000000000000000000000000000000000000..d25a3f956f88b2044da971544cb110250a9aed9f GIT binary patch literal 183 zcmZ?wbhEHb6krfwSoELa|NsAI&itP_^9%z6!+`?_fFuMc{$ycfU|?a;0dYZU8JIOa zcHQxNyjO5hObAEy!h3U#YVMe3EiGwEYhZdSoxkDLrH2+*Eam?>I59CO{_}UvFQ_ca zOwTA$FfuSOP)Mp&2rkW2@Xbsv$}g@gE=kQT)=}`xOV(3x%*jy*0x2w32udwZEh|J+e%d9h-XRVppwt5B-wa)CSPBS)U zU|=|K-~f;W0>z&!j38PEM1ah2U@;Ro;ki1cY*W+c7=;MKl+3cYs>t>Ofis-8xCU@6 iS@nKT`Jq{pSGF-Fa2UT>*OHXa%hkcaU=092`!^Z@ literal 0 HcmV?d00001 diff --git a/testing/src/org/aspectj/testing/gui/resources/images/Open24.gif b/testing/src/org/aspectj/testing/gui/resources/images/Open24.gif new file mode 100644 index 0000000000000000000000000000000000000000..cbb2e08ba2df6de4f10a0d82cdcfa1bfb1ef632b GIT binary patch literal 230 zcmZ?wbhEHblwgoxxcZ;rKM*`W`~Mjbo%#3l^xr3^|2#hR`_ZXi4^RGhaN^s&`x}SeUO)Ky>VX$m_CL9>=h3;{56%kb3A>i`?^x8gV^Qz+ zMZMb=_H3HlwSIQzx>@aOX11-G-nwXF!-5I*^ZM%M_SVkqs!lUDW?*1AaNq!tL<5RH YSr{1@EE#k_;vhdcu+2Cyr@%u80LGAoVE_OC literal 0 HcmV?d00001 diff --git a/testing/src/org/aspectj/testing/gui/resources/images/Quit24.gif b/testing/src/org/aspectj/testing/gui/resources/images/Quit24.gif new file mode 100644 index 0000000000000000000000000000000000000000..90515c8acf860707391fa4744957e56ead156313 GIT binary patch literal 313 zcmV-90mlAENk%w1VHf}y0JHx900030|No%?0Im%Uxkg6DQ&X{DU%qT?$aHkofq~+t zrrol#^78Tx4GqA+zyJUMA^8LW000gEEC2ui02lxm000D&(8)=wy|V$$H^WtfBx6Z7 zALxjSak%gyprEJ_gOKk;F{dHKZKo>^dcWY%$UORJ5*Du6B*>tEgn`IyZUKQPLe?z{ zuBq&nTKT5nQJdQXu$U|-(j9hvMs9mkcRE%{4h#(f1%_-$P!f!ekX({L6P1r; znHZHXqA`k~jb@V|j2o;Rnkozdv9q!&wzd%fA^uN8Z*X~XX=iA3ATls8Fd$-iAX9a2 zAWdmwa&L2ab97~Gb1Wc9ZeuPWL2PUwQfX&sbaNn5Wpib6c4cHP4GKz3Mj%I0AXa5^ LATcg54GI7|P-%Cp literal 0 HcmV?d00001 diff --git a/testing/src/org/aspectj/testing/gui/resources/images/Reload24.gif b/testing/src/org/aspectj/testing/gui/resources/images/Reload24.gif new file mode 100644 index 0000000000000000000000000000000000000000..3ead627fb04a0036eb27ed31fd404957f9c15cde GIT binary patch literal 245 zcmZ?wbhEHblwgoxSoELa|NsAI&itP_^Ng`^8Uq8vfddDCBoHY6WMO1rU}ew&aX@Mr zm{TKm-TAkn$K$5L*1iT~WrdBLv21J^S`*ZE?JM{xz?d`H!R7da4MLW&Z`n0|HB6B$ zEac?nn(p&l_<5$r#H&X`qe3^WSv$Q)Z8k6KoC_P?d{}la{;uw^xq^8MtXKqixkNX! zx;9lch;T9}{_}UvFQ_caOwTA$FfuSOP)Mp&2rkW2@Xbsv$}g@gE=kQT)=}`xOV(3x q%*jy*0x2w32udwZEhwoPk7#DV0y9hrl_L@i#VrFyvHoHu9Y#Xr$ntic|`qh+poMi9i^@_{^mw_veB2b=gA~G2ixdp?40yR1tO0)kMS%bS literal 0 HcmV?d00001 diff --git a/testing/src/org/aspectj/testing/gui/resources/images/Save24.gif b/testing/src/org/aspectj/testing/gui/resources/images/Save24.gif new file mode 100644 index 0000000000000000000000000000000000000000..8813f46d2e9201e4f7453696235b55e2242839ea GIT binary patch literal 80 zcmZ?wbhEHblwgoxSoELa|NsAI&YYP!b7opv8Uq8vfddDCBoHY6WMO1rU}ew&aX@Mv fm}??--MPoYBKT^@;x$>7f8f9Y1A|0c+q_SoKAk^*{>+&(hYlUuzkh#TUS48i zA}=p5NF5j`{$ycfU|?d<0r5a)Ft9`hobY_j!t+{2i^Dr0#YeCvWTgVz1vU@Q+HS$* zqqCo-@t+o-kYp(HN^RA}wBQLB{8}7Kx`b4`+3&B=tf*sXclmKeaE^!a#tLh}iNblo zrdwU2w|v}`s^7`Lp!m<$>7f8f9Y1A|0c+q_SoKAk^*{>+&(hYlUuzkmO{dGjVt zoS2uFmzbEy%gYN?toW0Kk%57UK?kH0WCjDvwty3!tJm^9S^HaWdN7N(ff>)LS&LX% zI+`RrPMs-D+%qjh$D~>7M#EOE^%Cc{UHNEW^}r`pTd8l#lEfpXmFfOn{NgH)H%wlV Mv@vh<{oM}w0On>xWB>pF literal 0 HcmV?d00001 diff --git a/testing/src/org/aspectj/testing/gui/resources/images/Start16.gif b/testing/src/org/aspectj/testing/gui/resources/images/Start16.gif new file mode 100644 index 0000000000000000000000000000000000000000..a72a9ca5cc7b40f13059887aaf8f38e1ccc30d88 GIT binary patch literal 140 zcmZ?wbhEHb6krfw*v!E2|Nnmm2L^^{28JvKh86~fWef~Q7!Djbz<>iN{$ybUN$G$H zkQodtmI59wOZSR*Hf&&zI#?T-AfU`S>&S$LfpsOY literal 0 HcmV?d00001 diff --git a/testing/src/org/aspectj/testing/gui/resources/images/Start24.gif b/testing/src/org/aspectj/testing/gui/resources/images/Start24.gif new file mode 100644 index 0000000000000000000000000000000000000000..63230a9e8e0a8b2ba751e4da1a252af6cd9f3650 GIT binary patch literal 165 zcmZ?wbhEHblwgox*v!E2|Nnmm2L^^{28JvKh86~fWef~Q7!Djbz<>iN{$ybUN$G$H zkQodtK_5N*)1tN|vUGW-Al literal 0 HcmV?d00001 diff --git a/testing/src/org/aspectj/testing/gui/resources/images/Stop16.gif b/testing/src/org/aspectj/testing/gui/resources/images/Stop16.gif new file mode 100644 index 0000000000000000000000000000000000000000..49184524442c16051a08b13c4709522b089b2f2c GIT binary patch literal 171 zcmZ?wbhEHb6krfw*vtR||NsAA$iT3Umv^U|+p*x_jq&mOa&j*A_1&92`_rdSyu7>z z4jce01QCiqSr{1@7#Vax9FQ3dEa3(xJhNjNvQrN`adPl-al6-c7OptvFiXxuq$ye; zPm@ve?JeB{Pl{Ja#=6EfT(`Q`-DlPeyJSYK$SY}4cf1#U;EPx-qZ3fJO803` Q3}^c7>vQ+0FfmvI0Ne^Y{r~^~ literal 0 HcmV?d00001 diff --git a/testing/src/org/aspectj/testing/gui/resources/images/Stop24.gif b/testing/src/org/aspectj/testing/gui/resources/images/Stop24.gif new file mode 100644 index 0000000000000000000000000000000000000000..3fae3827ecf5756709711799c454c45805d9a075 GIT binary patch literal 488 zcmd6ky-vbV0EMrKiIW2$iir%!LX0uQ!N|aZ3FzR$8}I@qjD$s7exxlb#{Nj`t=vYi zw+TqfP$&bG02A%zPjvPH+?*=Bg=hNC_MM!>N&GO9@&O zWbe8H-xqj;=Xw$xNzjzh1l7hWwy|QV*uu&z98N`}XQ@;ron|u`n8}FQtVEIxf~Zqe ziy)pDrd=+#8RmtiTNKr#>85G^aGb%iJwfR6yuoojmhIZM1K~&%O`e}u9igctjUY5d zaVQ9eEKd=d)M{f0hZx(6V#)GERc(wdMVWOvAF66$JcoBpd(-tVO?%b#&kLeXkc~UG z%grYU2FrDkJQd*($rEKRuUZP84F&`7KN9#|UIHKqb{9H}Pr!8lZY_uY E0Q&~MDgXcg literal 0 HcmV?d00001 diff --git a/testing/src/org/aspectj/testing/gui/resources/images/Test16.gif b/testing/src/org/aspectj/testing/gui/resources/images/Test16.gif new file mode 100644 index 0000000000000000000000000000000000000000..b728bcbe21f111a19540f21ec1a452116785ea3e GIT binary patch literal 933 zcmZ?wbhEHb6krfw_|5wj+F_;c#Sn{#JAoIU;i%&B*`Z~nb`{r9O8 z?~fmSb^7GHYgc|=zxr$Uu7^jDyg7dC?ZE@j_V0VLZ|~zHhhH8(^!)s}FIO)AxODN` zt($+(pZ~vZ-L*}dZf@LoWB>l=2M@kDeE8Lc3*Y81IJI!mnU$-qY~A)?%hr3FHs4vl z;rfo952w#OylBbU6{{|P`t<4W;ltauZR6$T1v_n&7!3j1hJfNv7DfgJMg|>_HK07f zz|q9O%pv2kVZp&>cE%F{Cj!`#oUB&b~mM|PxxX7_X&1=C62B*X$O`_3r z0tA_u*e0_ouKS^|=uig}i;d8l7m7@*yu4;I4HBPQTR0^JQW7ROK0nYUE+gY{Uwj+F_;c#Sn{#JAoIU;i%&B*`Z~nb`{r9O8 z?~fmSb^7GHYgc|=zxr$Uu7^jDyg7dC?ZE@j_V0VLZ|~zHhhH8(^!)s}FIO)AxODN` zt($+(pZ~vZ-L*}dZf@LoWB>l=2M@kDeE8Lc3*Y81IJI!mnU$-qY~A)?%hr3FHs4vl z;rfo952w#OylBbU6{{|P`t<4W;ltauZR6$TZD3&74R+orF&YB23jxKSEQ|~cj0`#; zD?oXIfuoOsnM1~7!-9j&?2IP@P6V(yN$NAX^l&gFx{B#}EMYjXaFJt&n%9CC3{Hth gnna`L1PC%Qu}x-GT=zp^(V-3|78{{8FBF+r0RdT)=>Px# literal 0 HcmV?d00001 diff --git a/testing/src/org/aspectj/testing/gui/resources/images/Testopen16.gif b/testing/src/org/aspectj/testing/gui/resources/images/Testopen16.gif new file mode 100644 index 0000000000000000000000000000000000000000..cd090c8840b2a37b99c951f044e329a967606a40 GIT binary patch literal 935 zcmZ?wbhEHb6krfw_|5wj+F_;c#Sn{#JAoIU;i%&B*`Z~nb`{r9O8 z?~fmSb^7GHYgc|=zxr$Uu7^jDyg7dC?ZE@j_V0VLZ|~zHhhH8(^!)s}FIO)AxODN` zt($+(pZ~vZ-L*}dZf@LoWB>l=2M@kDeE8Lc3*Y81IJI!mnU$-qY~A)?%hr3FHs4vl z;rfo952w#OylBbU6{{|P`t<4W;ltauZR6$T1v_n&7!3j1hJfNv7DfgJMg|>_HK07f zz|q9O%pv2kVZp&>cE%F{Cj!`#oUB&b~mM|PxxX7_X&1=C62B*X$O`_3r z0tA_u*e0_ouKS^|=nw~k1#j3FfyO2_2Aw#D-k(l~T6j2Ac)1v|8WN6j^YN(&SWJ1r o%*-so!Vu+<&cMverK2NIVU%{bTaMpP$>7f8fA@Ti5^GzVYYOi8trYd^mgh{h3qmZr}WS^ZM^o zC*B`F`s(z_ch|1`yngl9?p+U$9(i;8*xQ2#p6%cFWZ&M$M-IO{eCYZ4b6>7p{&DHz zw_7*=o+r!IrJ}Hf_GM ze#7-0J0DJ;d3e#1vny6z{`Be7;lqcwZQI7n%L{TD5kT=L3nK#q6N3&&Eyzy{Y{d?Z H1s*y8zh`~= literal 0 HcmV?d00001 diff --git a/testing/src/org/aspectj/testing/harness/bridge/AbstractRunSpec.java b/testing/src/org/aspectj/testing/harness/bridge/AbstractRunSpec.java new file mode 100644 index 000000000..944f096d4 --- /dev/null +++ b/testing/src/org/aspectj/testing/harness/bridge/AbstractRunSpec.java @@ -0,0 +1,883 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.harness.bridge; + +import org.aspectj.bridge.IMessage; +import org.aspectj.bridge.IMessageHandler; +import org.aspectj.bridge.ISourceLocation; +import org.aspectj.bridge.MessageHandler; +import org.aspectj.bridge.MessageUtil; +import org.aspectj.testing.run.IRunIterator; +import org.aspectj.testing.util.BridgeUtil; +import org.aspectj.testing.xml.IXmlWritable; +import org.aspectj.testing.xml.SoftMessage; +import org.aspectj.testing.xml.XMLWriter; +import org.aspectj.util.LangUtil; + +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.BitSet; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +/** + * Base class for initialization of components expecting messages, + * options, files/paths, and source locations (resolved files), + * and potentially containing child Spec. + *

    + * initialization: This defines bean/xml setters for all. + * This converts String to IMessage using + * {@link MessageUtil#readMessage(String)} + * and String to ISourceLocation using + * {@link BridgeUtil#makeSourceLocation(input)}. + * See those APIs for input form and limitations. + * A Spec also accepts (or rejects) runtime configuration from a parent + * in {@link adoptParentValues(RT, IMessageHandler)}. + * Since some children Spec may balk but this parent Spec continue, + * use {@link getChildren()} to get the full list of children Spec, + * but {@link getWorkingChildren()} to get the list of children that + * are not being skipped in accordance with runtime configuration. + *

    + * subclassing: subclasses wishing other than + * the default behavior for reading String input should override the + * corresponding (bean) setter. They can also override the + * add{foo} methods to get notice or modify objects constructed + * from the input. + *

    + * bean properties: because this is designed to work + * by standard Java bean introspection, take care to follow + * bean rules when adding methods. In particular, a property + * is illegal if the setter takes a different type than the + * getter returns. That means, e.g., that all List and array[] getters + * should be named "get{property}[List|Array]". Otherwise + * the XML readers will silently fail to set the property + * (perhaps with trace information that the property had + * no write method or was read-only). + *

    + * Coordination with writers: because this reads the contents + * of values written by IXmlWritable, they should ensure their + * values are readable. When flattening and unflattening + * lists, the convention is to use the {un}flattenList(..) methods + * in XMLWriter. + * @see XMLWriter@unflattenList(String) + * @see XMLWriter@flattenList(List) + */ +abstract public class AbstractRunSpec implements IRunSpec { // XXX use MessageHandler? + + /** true if we expect to use a staging directory */ + boolean isStaging; + + protected String description; + + /** optional source location of the specification itself */ + protected ISourceLocation sourceLocation; + + private BitSet skipSet; + private boolean skipAll; + + protected final String xmlElementName; + protected final ArrayList /*String*/ keywords; + protected final ArrayList /*IMessage*/ expectedMessages; + protected final ArrayList /*String*/ options; + protected final ArrayList /*String*/ paths; + protected final ArrayList /*ISourceLocation*/ sourceLocations; // XXX remove? + protected final ArrayList /*IRunSpec*/ children; + protected final ArrayList /*DirChanges.Spec*/ dirChanges; + protected XMLNames xmlNames; + protected String comment; + + /** These options are 1:1 with spec, but set at runtime (not saved) */ + public final RT runtime; + + /** if true, then any child skip causes this to skip */ + protected final boolean skipIfAnyChildSkipped; + + public AbstractRunSpec(String xmlElementName) { + this(xmlElementName, true); + } + + public AbstractRunSpec(String xmlElementName, boolean skipIfAnyChildSkipped) { + if (null == xmlElementName) { + xmlElementName = "spec"; + } + this.xmlElementName = xmlElementName; + expectedMessages = new ArrayList(); + options = new ArrayList(); + paths = new ArrayList(); + sourceLocations = new ArrayList(); + keywords = new ArrayList(); + children = new ArrayList(); + dirChanges = new ArrayList(); + xmlNames = XMLNames.DEFAULT; + runtime = new RT(); + this.skipIfAnyChildSkipped = skipIfAnyChildSkipped; + } + + + /** @param comment ignored if null */ + public void setComment(String comment) { + if (!LangUtil.isEmpty(comment)) { + this.comment = comment; + } + } + + public void setStaging(boolean staging) { + isStaging = staging; + } + + boolean isStaging() { + return isStaging; + } + + // ------- description (title, label...) + public void setDescription(String description) { + this.description = description; + } + + public String getDescription() { + return description; + } + + // ------- source location of the spec + + public void setSourceLocation(ISourceLocation sourceLocation) { + this.sourceLocation = sourceLocation; + } + + public ISourceLocation getSourceLocation() { + return sourceLocation; + } + + // ------- keywords + /** @param keyword added after trimming if not empty */ + public void setKeyword(String keyword) { + addKeyword(keyword); + } + + /** Add keyword if non-empty and not duplicate */ + public void addKeyword(String keyword) { + if (!LangUtil.isEmptyTrimmed(keyword)) { + keyword = keyword.trim(); + if (!keywords.contains(keyword)) { + keywords.add(keyword); + } + } + } + public void setKeywords(String items) { + addKeywords(items); + } + + public void addKeywords(String items) { + if (null != items) { + addKeywords(XMLWriter.unflattenList(items)); + } + } + public void addKeywords(String[] ra) { + if (null != ra) { + for (int i = 0; i < ra.length; i++) { + addKeyword(ra[i]); + } + } + } + public ArrayList getKeywordsList() { + return makeList(keywords); + } + + // ------- options - String args + + /** @return ArrayList of String options */ + public ArrayList getOptionsList() { + return makeList(options); + } + + /** @return String[] of options */ + public String[] getOptionsArray() { + return (String[]) options.toArray(new String[0]); + } + + public void setOption(String option) { + addOption(option); + } + + public void addOption(String option) { + if ((null != option) && (0 < option.length())) { + options.add(option); + } + } + + /** add options (from XML/bean) - removes any existing options */ + public void setOptions(String items) { + this.options.clear(); + addOptions(items); + } + + /** + * Set options, removing any existing options. + * @param options String[] options to use - may be null or empty + */ + public void setOptionsArray(String[] options) { + this.options.clear(); + if (!LangUtil.isEmpty(options)) { + this.options.addAll(Arrays.asList(options)); + } + } + + public void addOptions(String items) { + if (null != items) { + addOptions(XMLWriter.unflattenList(items)); + } + } + + public void addOptions(String[] ra) { + if (null != ra) { + for (int i = 0; i < ra.length; i++) { + addOption(ra[i]); + } + } + } + + // -------------- source locations +// /** @return ArrayList of ISourceLocation sourceLocs */ +// public ArrayList getSourceLocationsList() { +// return makeList(sourceLocations); +// } +// +// /** @return ISourceLocation[] sourceLocs */ +// public ISourceLocation[] getSourceLocationsArray() { +// return (ISourceLocation[]) sourceLocations.toArray(new ISourceLocation[0]); +// } +// +// +// public void setSourceLocation(String input) { +// if (null != input) { +// ISourceLocation sl = BridgeUtil.makeSourceLocation(input); +// if (null != sl) { +// addSourceLocation(sl); +// } +// // XXX need error-handling for bad input +// } +// } +// +// public void addSourceLocation(ISourceLocation sourceLoc) { +// if (null != sourceLoc) { +// sourceLocations.add(sourceLoc); +// } +// } + + // --------------- (String) paths + /** @return ArrayList of String paths */ + public ArrayList getPathsList() { + return makeList(paths); + } + + /** @return String[] of paths */ + public String[] getPathsArray() { + return (String[]) paths.toArray(new String[0]); + } + +// /** @return String[] of paths, removing any matching stripPrefix prefix */ +// public String[] getPathsArray(String stripPrefix) { +// String[] result = getPathsArray(); +// if (!LangUtil.isEmpty(stripPrefix)) { +// final int LEN = stripPrefix.length(); +// for (int i = 0; i < result.length; i++) { +// if ((null != result[i]) && result[i].startsWith(stripPrefix)) { +// result[i] = result[i].substring(LEN); +// } +// } +// } +// return result; +// } +// +// /** @return ArrayList of File baseDir/{path} */ +// public ArrayList getPathsAsFile(File baseDir) { +// if (null == baseDir) { +// baseDir = new File("."); +// } +// ArrayList result = makeList(null); +// for (Iterator iter = paths.iterator(); iter.hasNext();) { +// String path = (String) iter.next(); +// result.add(new File(baseDir, path)); +// } +// return result; +// } + + public void setPath(String path) { + addPath(path); + } + + public void setPaths(String paths) { + addPaths(paths); + } + + public void addPath(String path) { + if (null != path) { + paths.add(path); + } + } + + public void addPaths(String items) { + if (null != items) { + addPaths(XMLWriter.unflattenList(items)); + } + } + + public void addPaths(String[] ra) { + if (null != ra) { + for (int i = 0; i < ra.length; i++) { + addPath(ra[i]); + } + } + } + + public void addWrapFile(WrapFile file) { + if (null != file) { + paths.add(file.path); + } + } + + // --------------------- dir changes + public void addDirChanges(DirChanges.Spec dirChangesSpec) { + if (null != dirChangesSpec) { + dirChanges.add(dirChangesSpec); + } + } + + // --------------------- messages + public void setMessage(String message) { + addMessage(message); + } + + public void addMessage(IMessage message) { + if (null != message) { + expectedMessages.add(message); + } + } + + public void addMessage(String message) { + if (null != message) { + IMessage m = BridgeUtil.readMessage(message); + expectedMessages.add(m); + } + } + + /** this can ONLY work if each item has no internal comma + */ + public void addMessages(String items) { + if (null != items) { + String[] ra = XMLWriter.unflattenList(items); + for (int i = 0; i < ra.length; i++) { + addMessage(ra[i]); + } + } + } + public void addMessages(List messages) { + if (null != messages) { + for (Iterator iter = messages.iterator(); iter.hasNext();) { + Object o = iter.next(); + if (o instanceof IMessage) { + addMessage((IMessage) o); + } else { + // XXX warning? + } + } + } + } + + public ArrayList getMessages(IMessage.Kind kind) { + return makeList(MessageUtil.getMessages(expectedMessages, kind)); + } + + + /** @return int number of message of this kind (optionally or greater */ + public int numMessages(IMessage.Kind kind, boolean orGreater) { + return MessageUtil.numMessages(expectedMessages, kind, orGreater); + } + + public ArrayList getMessages() { + return getMessages(null); // XXX special meaning of null + } + + + public void addChild(IRunSpec child) { + // fyi, child is added when complete (depth-first), not when initialized, + // so cannot affect initialization of child here + if (null != child) { + children.add(child); + } + } + + /** @return copy of children list */ + public ArrayList getChildren() { + return makeList(children); + } + + /** @return copy of children list without children to skip */ + public ArrayList getWorkingChildren() { + if (skipAll) { + return new ArrayList(); + } + if (null == skipSet) { + return getChildren(); + } + ArrayList result = new ArrayList(); + int i = 0; + for (Iterator iter = children.listIterator(); + iter.hasNext(); i++) { + Object child = iter.next(); + if (!skipSet.get(i)) { + result.add(child); + } + } + return result; + } + + /** + * Recursively absorb parent values if different. + * This implementation calls doAdoptParentValues(..) + * and then calls this for any children. + * This is when skipped children are determined. + * Children may elect to balk at this point, reducing the + * number of children or causing this spec to skip if + * skipIfAnyChildrenSkipped. For each test skipped, either + * this doAdoptParentValues(..) or the child's adoptParentValues(..) + * should add one info message with the reason this is being skipped. + * The only reason to override this would be to NOT + * invoke the same for children, or to do something similar + * for children which are not AbstractRunSpec. + * @param parentRuntime the RT values to adopt - ignored if null + * @param handler the IMessageHandler for info messages when skipping + * @return false if this wants to be skipped, true otherwise + */ + public boolean adoptParentValues(RT parentRuntime, IMessageHandler handler) { + boolean skipped = false; + skipAll = false; + skipSet = new BitSet(); + if (null != parentRuntime) { + skipped = !doAdoptParentValues(parentRuntime, handler); + if (skipped && skipIfAnyChildSkipped) { // no need to continue checking + skipAll = true; + return false; + } + int i = 0; + for (ListIterator iter = children.listIterator(); iter.hasNext(); i++) { + IRunSpec child = (IRunSpec) iter.next(); + if (child instanceof AbstractRunSpec) { // XXX ugly instanceof + AbstractRunSpec arsChild = (AbstractRunSpec) child; + if (!arsChild.adoptParentValues(runtime, handler)) { + skipSet.set(i); + //iter.remove(); + if (!skipped) { + skipped = true; + if (skipIfAnyChildSkipped) { // no need to continue checking +// String m = "skipping " + toString() + " because child " +// + arsChild + " skipped"; +// MessageUtil.info(handler, m); + skipAll = true; + //children.clear(); + return false; + } + } + } + } + } + } + return true; + } + + /** + * Adopt parent values. + * This implementation makes a local copy. + * If we interpret (and absorb) any options, they should be removed + * from parentRuntime. + * This sets verbose if different (override) + * and directly adopts parentOptions if ours is null + * and otherwise adds any non-null options we don't already have. + * setting verbose and adding to parent options. + * Implementors override this to affect how parent values are adopted. + * Implementors should not recurse into children. + * This method may be called multiple times, so implementors + * should not destroy any spec information. + * Always add an info message when returning false to skip + * @param parentRuntime the RT values to adopt - never null + * @return false if this wants to be skipped, true otherwise + */ + protected boolean doAdoptParentValues(RT parentRuntime, IMessageHandler handler) { + if (runtime.verbose != parentRuntime.verbose) { + runtime.verbose = parentRuntime.verbose; + } + if (!LangUtil.isEmpty(runtime.parentOptions)) { + runtime.parentOptions.clear(); + } + if (!LangUtil.isEmpty(parentRuntime.parentOptions)) { + runtime.parentOptions.addAll(parentRuntime.parentOptions); + } + return true; + } + + /** + * Implementations call this when signalling skips to ensure consistency + * in message formatting + * @param handler the IMessageHandler sink - not null + * @param reason the String reason to skip - not null + */ + protected void skipMessage(IMessageHandler handler, String reason) { + LangUtil.throwIaxIfNull(handler, "handler"); + LangUtil.throwIaxIfNull(handler, "reason"); + // XXX for Runs, label does not identify the test + String label = toString(); + MessageUtil.info(handler, "skipping \"" + label + "\" because " + reason); + } + + // --------------------------- writing xml - would prefer castor.. + + /** + * Control XML output by renaming or suppressing output for + * attributes and subelements. + * Subelements are skipped by setting the XMLNames booleans to false. + * Attributes are skipped by setting their name to null. + * @param names XMLNames with new names and/or suppress flags. + */ + protected void setXMLNames(XMLNames names) { + if (null != names) { + xmlNames = names; + } + } + + /** @return null if value is null or name="{value}" otherwise */ + private String makeAttr(XMLWriter out, String name, String value) { + if (null == value) { + return null; + } + return out.makeAttribute(name, value); + } + + /** @return null if list is null or empty or name="{flattenedList}" otherwise */ + private String makeAttr(XMLWriter out, String name, List list) { + if (LangUtil.isEmpty(list)) { + return null; + } + String flat = out.flattenList(list); + return out.makeAttribute(name, flat); + } + + /** @return true if writeAttributes(..) will produce any output */ + protected boolean haveAttributes() { + return ((!LangUtil.isEmpty(xmlNames.descriptionName) + && !LangUtil.isEmpty(description)) + || (!LangUtil.isEmpty(xmlNames.keywordsName) + && !LangUtil.isEmpty(keywords)) + || (!LangUtil.isEmpty(xmlNames.optionsName) + && !LangUtil.isEmpty(options)) + || (!LangUtil.isEmpty(xmlNames.pathsName) + && !LangUtil.isEmpty(paths))); + } + + /** + * Write attributes without opening or closing elements/attributes. + * An attribute is written only if the value is not empty + * and the name in xmlNames is not empty + */ + protected void writeAttributes(XMLWriter out) { + if (!LangUtil.isEmpty(xmlNames.descriptionName) + && !LangUtil.isEmpty(description)) { + out.printAttribute(xmlNames.descriptionName, description); + } + if (!LangUtil.isEmpty(xmlNames.keywordsName) + && !LangUtil.isEmpty(keywords)) { + out.printAttribute(xmlNames.keywordsName, out.flattenList(keywords)); + } + if (!LangUtil.isEmpty(xmlNames.optionsName) + && !LangUtil.isEmpty(options)) { + out.printAttribute(xmlNames.optionsName, out.flattenList(options)); + } + if (!LangUtil.isEmpty(xmlNames.pathsName) + && !LangUtil.isEmpty(paths)) { + out.printAttribute(xmlNames.pathsName, out.flattenList(paths)); + } + if (!LangUtil.isEmpty(xmlNames.commentName) + && !LangUtil.isEmpty(comment)) { + out.printAttribute(xmlNames.commentName, comment); + } + } + + /** + * The default implementation writes everything as attributes, + * then subelements for dirChanges, messages, then subelements for children. + * Subclasses that override may delegate back for any of these. + * Subclasses may also set XMLNames to name or suppress any attribute + * or subelement. + * @see writeMessages(XMLWriter) + * @see writeChildren(XMLWriter) + * @see IXmlWritable#writeXml(XMLWriter) + */ + public void writeXml(XMLWriter out) { + out.startElement(xmlElementName,false); + writeAttributes(out); + out.endAttributes(); + if (!xmlNames.skipMessages) { + writeMessages(out); + } + if (!xmlNames.skipChildren) { + writeChildren(out); + } + out.endElement(xmlElementName); + } + + /** + * Write messages. Assumes attributes are closed, + * can write child elements of current element. + */ + protected void writeMessages(XMLWriter out) { + if (0 < expectedMessages.size()) { + SoftMessage.writeXml(out, expectedMessages); + } + + } + + /** + * Write children. Assumes attributes are closed, + * can write child elements of current element. + */ + protected void writeChildren(XMLWriter out) { + if (0 < children.size()) { + for (Iterator iter = children.iterator(); iter.hasNext();) { + IXmlWritable self = (IXmlWritable) iter.next(); + self.writeXml(out); + } + } + } + + // --------------------------- logging + + public void printAll(PrintStream out, String prefix) { + out.println(prefix + toString()); + for (Iterator iter = children.iterator(); iter.hasNext();) { + AbstractRunSpec child = (AbstractRunSpec) iter.next(); // IRunSpec + child.printAll(out, prefix + " "); + } + } + + /** + * default implementation returns the description if not empty + * or the unqualified class name otherwise. + * Subclasses should not call toString from here unless they reimplement it. + * @return name of this thing or type + */ + protected String getPrintName() { + if (!LangUtil.isEmpty(description)) { + return description; + } else { + return LangUtil.unqualifiedClassName(this); + } + } + + /** @return summary count of spec elements */ + public String toString() { + return getPrintName() + "(" + containedSummary() + ")"; + } + + /** @return String of the form (# [options|paths|locations|messages]).. */ + protected String containedSummary() { + int nOptions = options.size(); + int nPaths = paths.size(); + int nLoc = sourceLocations.size(); + int nMssg = expectedMessages.size(); + return + ( (nOptions == 0 ? "" : nOptions + " options " ) + + (nPaths == 0 ? "" : nPaths + " paths " ) + + (nLoc == 0 ? "" : nLoc + " locations " ) + + (nMssg == 0 ? "" : nMssg + " messages" )).trim(); + } + + public String toLongString() { + String mssg = ""; + if (expectedMessages.size() > 0) { + // XXX add MessageHandler.renderCounts(IMessage[] messages) + MessageHandler h = new MessageHandler(); + for (Iterator iter = expectedMessages.iterator(); iter.hasNext();) { + h.handleMessage((IMessage) iter.next()); + } + mssg = " expected messages (" + MessageUtil.renderCounts(h) + ")"; + } + return getPrintName() + containedSummary() + mssg.trim(); + } + + private ArrayList makeList(List list) { + ArrayList result = new ArrayList(); + if (null != list) { + result.addAll(list); + } + return result; + } + + /** + * Subclasses use this to rename attributes or omit attributes or subelements. + * To suppress output of an attribute, * pass "" as the name of the attribute. + * To use default entries, pass null for that entry. + */ + public static class XMLNames { + public static final XMLNames DEFAULT = + new XMLNames(null, "description", "sourceLocation", + "keywords", "options", "paths", "comment", false, false, false); + final String descriptionName; + final String sourceLocationName; + final String keywordsName; + final String optionsName; + final String pathsName; + final String commentName; + final boolean skipDirChanges; + final boolean skipMessages; + final boolean skipChildren; + /** reset all names/behavior or pass defaultNames + * as the defaults for any null elements + */ + XMLNames(XMLNames defaultNames, + String descriptionName, + String sourceLocationName, + String keywordsName, + String optionsName, + String pathsName, + String commentName, + boolean skipDirChanges, + boolean skipMessages, + boolean skipChildren) { + this.skipDirChanges = skipDirChanges; + this.skipMessages = skipMessages; + this.skipChildren = skipChildren; + if (null != defaultNames) { + this.descriptionName = (null != descriptionName? descriptionName : defaultNames.descriptionName); + this.sourceLocationName = (null != sourceLocationName ? sourceLocationName : defaultNames.sourceLocationName); + this.keywordsName = (null != keywordsName ? keywordsName : defaultNames.keywordsName); + this.optionsName = (null != optionsName ? optionsName : defaultNames.optionsName); + this.pathsName = (null != pathsName ? pathsName : defaultNames.pathsName); + this.commentName = (null != commentName ? commentName : defaultNames.commentName); + } else { + this.descriptionName = descriptionName; + this.sourceLocationName = sourceLocationName; + this.keywordsName = keywordsName; + this.optionsName = optionsName; + this.pathsName = pathsName; + this.commentName = commentName; + } + } + } + + + /** subclasses implement this to create and set up a run */ + abstract public IRunIterator makeRunIterator(Sandbox sandbox, Validator validator); + + /** This is for separate file (sub-) elements with path attributes */ + public static class WrapFile { + public String path; + public void setPath(String path) { + this.path = path; + } + } + + /** segregate runtime-only state in spec */ + public static class RT { + /** true if we should emit verbose messages */ + private boolean verbose; + + /** null unless parent set options for children to consider */ + final private ArrayList /*String*/ parentOptions; + + public RT() { + parentOptions = new ArrayList(); + } + + public boolean isVerbose() { + return verbose; + } + + /** + * Set parent options - old options destroyed. + * Will result in duplicates if duplicates added. + * Null or empty entries are ignored + * @param options ignored if null or empty + */ + public void setOptions(String[] options) { + parentOptions.clear(); + if (!LangUtil.isEmpty(options)) { + for (int i = 0; i < options.length; i++) { + if (!LangUtil.isEmpty(options[i])) { + parentOptions.add(options[i]); + } + } + } + } + + /** + * Copy values from another RT + * @param toCopy the RT to copy from + * @throws IllegalArgumentException if toCopy is null + */ + public void copy(RT toCopy) { + LangUtil.throwIaxIfNull(toCopy, "parent"); + parentOptions.clear(); + parentOptions.addAll(toCopy.parentOptions); + verbose = toCopy.verbose; + } + + /** + * Return any parent option which has one of validOptions as a prefix, + * optionally absorbing (removing) the parent option. + * @param validOptions String[] of options to extract + * @param absorb if true, then remove any parent option matched + * @return String[] containing any validOptions[i] in parentOptions + * (at most once) + */ + public String[] extractOptions(String[] validOptions, boolean absorb) { + if (LangUtil.isEmpty(validOptions) || LangUtil.isEmpty(parentOptions)) { + return new String[0]; + } + ArrayList result = new ArrayList(); + boolean haveOption = false; + for (int i = 0; i < validOptions.length; i++) { + String option = validOptions[i]; + if (LangUtil.isEmpty(option)) { + continue; + } + for (ListIterator iter = parentOptions.listIterator(); iter.hasNext();) { + String parentOption = (String) iter.next(); + if (parentOption.startsWith(option)) { + result.add(parentOption); + if (absorb) { + iter.remove(); + } + } + } + } + return (String[]) result.toArray(new String[0]); + } + + /** Get ListIterator that permits removals */ + ListIterator getListIterator() { + return parentOptions.listIterator(); + } + + /** + * Enable verbose logging + * @param verbose if true, do verbose logging + */ + public void setVerbose(boolean verbose) { + if (this.verbose != verbose) { + this.verbose = verbose; + } + } + } // class RT +} diff --git a/testing/src/org/aspectj/testing/harness/bridge/AjcMessageHandler.java b/testing/src/org/aspectj/testing/harness/bridge/AjcMessageHandler.java new file mode 100644 index 000000000..943110df3 --- /dev/null +++ b/testing/src/org/aspectj/testing/harness/bridge/AjcMessageHandler.java @@ -0,0 +1,282 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.harness.bridge; + +import org.aspectj.bridge.IMessage; +import org.aspectj.bridge.IMessageHandler; +import org.aspectj.bridge.IMessageHolder; +import org.aspectj.bridge.MessageHandler; +import org.aspectj.bridge.MessageUtil; +import org.aspectj.testing.util.BridgeUtil; +import org.aspectj.testing.util.Diffs; +import org.aspectj.util.LangUtil; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; + + +/** + * Adapter to pass messages through to handler, + * suppress output of expected errors and warnings, + * and calculate differences between expected and + * actual messages. + */ +public class AjcMessageHandler extends MessageHandler { + + /** Comparator for enclosed IMessage diffs, etc. */ + public static final Comparator COMP_IMessage + = BridgeUtil.Comparators.WEAK_IMessage; + + /** Comparator for enclosed File diffs, etc. */ + public static final Comparator COMP_File + = BridgeUtil.Comparators.WEAK_File; + + + /** unmodifiable list of IMessage messages of type IMesssage.ERROR */ + final List expectedErrors; // revert to IMessageHolder for generality? + + /** unmodifiable list of IMessage messages of type IMesssage.WARNING */ + final List expectedWarnings; + + /** unmodifiable list of File expected to be recompiled */ + final List expectedRecompiled; + + /** list of File actually recompiled */ + List actualRecompiled; + + /** if true, ignore warnings when calculating diffs and passed() */ + boolean ignoreWarnings; + + /** cache expected/actual diffs, nullify if any new message */ + transient CompilerDiffs diffs; + + private boolean expectingCommandTrue; + + /** + * @param messages List of IMessage to extract ERROR and WARNING from + */ + AjcMessageHandler(List messages) { + this(MessageUtil.getMessages(messages, IMessage.ERROR), + MessageUtil.getMessages(messages, IMessage.WARNING)); + } + + /** + * @param errors unmodifiable List of IMessage of kind ERROR to adopt + * @param warnings unmodifiable List of IMessage of kind WARNING to adopt + */ + AjcMessageHandler(List errors, List warnings) { + this(errors, warnings, null); + } + + AjcMessageHandler(List errors, List warnings, List recompiled) { + this.expectedErrors = LangUtil.safeList(errors); + this.expectedWarnings = LangUtil.safeList(warnings); + this.expectedRecompiled = LangUtil.safeList(recompiled); + expectingCommandTrue = (0 == expectedErrors.size()); + } + + public void setIgnoreWarnings(boolean ignoreWarnings) { + this.ignoreWarnings = ignoreWarnings; + } + + /** clear out any actual values to be re-run */ + public void init() { + super.init(); + actualRecompiled = null; + diffs = null; + } + + /** + * Return true if we have this kind of + * message for the same line and store all messages. + * @see bridge.tools.impl.ErrorHandlerAdapter#doShowMessage(IMessage) + * @return true if message handled (processing should abort) + */ + public boolean handleMessage(IMessage message) { + if (null == message) { + throw new IllegalArgumentException("null message"); + } + messages.add(message); + IMessage.Kind kind = message.getKind(); + return expecting(message, getExpected(kind)); + } + + /** + * Set the actual files recompiled. + * @param List of File recompiled - may be null; adopted but not modified + * @throws IllegalStateException if they have been set already. + */ + public void setRecompiled(List list) { + if (null != actualRecompiled) { + throw new IllegalStateException("actual recompiled already set"); + } + this.actualRecompiled = LangUtil.safeList(list); + } + + /** @return immutable List of IMessage expected of this kind */ + public List getExpected(IMessage.Kind kind) { + if (IMessage.ERROR == kind) { + return expectedErrors; + } else if (IMessage.WARNING == kind) { + return expectedWarnings; + } else { + return Collections.EMPTY_LIST; + } + } + + /** + * Check if the message was expected, and clear diffs if not. + * @return true if we expect a message of this kind with this line number + */ + protected boolean expecting(IMessage message, List sink) { // XXX ignores File + if ((null != message) && (0 < sink.size())) { + // XXX need to cache list: int[] errLines, warningLines; + for (Iterator iter = sink.iterator(); iter.hasNext();) { + IMessage m = (IMessage) iter.next(); + if (0 == COMP_IMessage.compare(message, m)) { + return true; + } + } + } + if (null != diffs) { + diffs = null; + } + return false; + } + + /** Generate differences between expected and actual errors and warnings */ + public CompilerDiffs getCompilerDiffs() { + if (null == diffs) { + final List actualErrors = Arrays.asList(getMessages(IMessage.ERROR, IMessageHolder.EQUAL)); + final List actualWarnings = Arrays.asList(getMessages(IMessage.WARNING, IMessageHolder.EQUAL)); + Diffs errors = new Diffs("error", expectedErrors, actualErrors, COMP_IMessage); + Diffs warnings = new Diffs("warning", expectedWarnings, actualWarnings, COMP_IMessage); + Diffs recompiled = new Diffs("recompiled", expectedRecompiled, actualRecompiled, COMP_File); + diffs = new CompilerDiffs(errors, warnings, recompiled); + } + return diffs; + } + + /** calculate passed based on default or set value for ignoreWarnings */ + public boolean passed() { + return passed(ignoreWarnings); + } + + /** @return true if we are expecting the command to fail - i.e., any expected errors */ + public boolean expectingCommandTrue() { + return expectingCommandTrue; + } + + /** + * Get the (current) result of this run, + * ignoring differences in warnings on request. + * Note it may return passed (true) when there are expected error messages. + * @return false + * if there are any fail or abort messages, + * or if the expected errors, warnings, or recompiled do not match actual. + * + */ + public boolean passed(boolean ignoreWarnings) { + if (hasAnyMessage(IMessage.FAIL, IMessageHolder.EQUAL)) { + return false; + } + + CompilerDiffs diffs = getCompilerDiffs(); + if (!ignoreWarnings) { + return (!diffs.different); + } else { + return ((!diffs.errors.different) && (!diffs.recompiled.different)); + } + } + + /** + * Report results to a handler, + * adding all messages + * and creating fail messages for each diff. + */ + public void report(IMessageHandler handler) { + if (null == handler) { + MessageUtil.debug(this, "report got null handler"); + } + IMessage[] messages = getMessages(null, IMessageHolder.EQUAL); + for (int i = 0; i < messages.length; i++) { + handler.handleMessage(messages[i]); + } + CompilerDiffs diffs = getCompilerDiffs(); + if (diffs.different) { + diffs.errors.report(handler, IMessage.FAIL); + diffs.warnings.report(handler, IMessage.FAIL); + diffs.recompiled.report(handler, IMessage.FAIL); + } + } + + /** @return String consisting of differences and any other messages */ + public String toString() { + CompilerDiffs diffs = getCompilerDiffs(); + StringBuffer sb = new StringBuffer(super.toString()); + final String EOL = "\n"; + sb.append(EOL); + render(sb, " unexpected error ", EOL, diffs.errors.unexpected); + render(sb, "unexpected warning ", EOL, diffs.warnings.unexpected); + render(sb, " missing error ", EOL, diffs.errors.missing); + render(sb, " missing warning ", EOL, diffs.warnings.missing); + render(sb, " fail ", EOL, getList(IMessage.FAIL)); + render(sb, " abort ", EOL, getList(IMessage.ABORT)); + render(sb, " info ", EOL, getList(IMessage.INFO)); + return sb.toString(); // XXX cache toString + } + + /** @return immutable list of a given kind - use null for all kinds */ + private List getList(IMessage.Kind kind) { + if ((null == kind) || (0 == numMessages(kind, IMessageHolder.EQUAL))) { + return Collections.EMPTY_LIST; + } + return Arrays.asList(getMessages(kind, IMessageHolder.EQUAL)); + } + + /** @return "" if no items or {prefix}{item}{suffix}... otherwise */ + private void render( // LangUtil instead? + StringBuffer result, + String prefix, + String suffix, + List items) { + if ((null != items)) { + for (Iterator iter = items.iterator(); iter.hasNext();) { + result.append(prefix + iter.next() + suffix); + } + } + } + + /** compiler results for errors, warnings, and recompiled files */ + public static class CompilerDiffs { + public final Diffs errors; + public final Diffs warnings; + public final Diffs recompiled; + public final boolean different; + + public CompilerDiffs(Diffs errors, Diffs warnings) { + this(errors, warnings, Diffs.NONE); + } + public CompilerDiffs(Diffs errors, Diffs warnings, Diffs recompiled) { + this.errors = errors; + this.warnings = warnings; + this.recompiled = recompiled; + different = (warnings.different || errors.different + || recompiled.different); + } + } +} diff --git a/testing/src/org/aspectj/testing/harness/bridge/AjcTest.java b/testing/src/org/aspectj/testing/harness/bridge/AjcTest.java new file mode 100644 index 000000000..b1e9e9b1b --- /dev/null +++ b/testing/src/org/aspectj/testing/harness/bridge/AjcTest.java @@ -0,0 +1,371 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.harness.bridge; + +import org.aspectj.bridge.IMessageHandler; +import org.aspectj.bridge.ISourceLocation; +import org.aspectj.testing.run.IRunIterator; +import org.aspectj.testing.run.IRunStatus; +import org.aspectj.testing.run.Runner; +import org.aspectj.testing.xml.XMLWriter; +import org.aspectj.util.LangUtil; + +import java.io.File; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +/** + * An AjcTest has child subruns (compile, [inc-compile|run]*). + */ +public class AjcTest extends RunSpecIterator { + + /** Unwrap an AjcTest.Spec from an IRunStatus around an AjcTest */ + public static Spec unwrapSpec(IRunStatus status) { + if (null != status) { + Object id = status.getIdentifier(); + if (id instanceof Runner.IteratorWrapper) { + IRunIterator iter = ((Runner.IteratorWrapper) id).iterator; + if (iter instanceof AjcTest) { + return (Spec) ((AjcTest) iter).spec; + } + } + } + return null; + } + + /** Unwrap initial CompilerRun.Spec from an AjcTest.Spec */ + public static CompilerRun.Spec unwrapCompilerRunSpec(Spec spec) { + if (null != spec) { + List kids = spec.getChildren(); + if (0 < kids.size()) { + Object o = kids.get(0); + if (o instanceof CompilerRun.Spec) { + return (CompilerRun.Spec) o; + } + } + } + return null; + } + + /** The spec creates the sandbox, so we use it throughout */ + public AjcTest(Spec spec, Sandbox sandbox, Validator validator) { + super(spec, sandbox, validator, true); + } + + /** + * Clear the command from the sandbox, to avoid memory leaks. + * @see org.aspectj.testing.harness.bridge.RunSpecIterator#iterationCompleted() + */ + public void iterationCompleted() { + super.iterationCompleted(); + sandbox.clearCommand(this); + } + + + /** + * Specification for an ajc test. + * Keyword directives are global/parent options passed as + *

    -ajctest[Require|Skip]Keywords=keyword{,keyword}..
    . + */ + public static class Spec extends AbstractRunSpec { + public static final String XMLNAME = "ajc-test"; + /** + * do description as title, do sourceLocation, + * do keywords, do options, skip paths, do comment, + * skip dirChanges, do messages and do children + * (though we do children directly). + */ + private static final XMLNames NAMES = new XMLNames(XMLNames.DEFAULT, + "title", null, null, null, "", null, true, false, false); + + private static final String OPTION_PREFIX = "-ajctest"; + private static final String[] VALID_OPTIONS = new String[] { OPTION_PREFIX }; + + private static final String REQUIRE_KEYWORDS = "RequireKeywords="; + private static final String SKIP_KEYWORDS = "SkipKeywords="; + private static final String PICK_PR = "PR="; + private static final List VALID_SUFFIXES + = Collections.unmodifiableList(Arrays.asList(new String[] + { REQUIRE_KEYWORDS, SKIP_KEYWORDS, PICK_PR })); + + /** base directory of the test suite - set before making run */ + private File suiteDir; + + /** path offset from suite directory to base of test directory */ + String testDirOffset; // XXX revert to private after fixes + + /** id of bug - if 0, then no bug associated with this test */ + private int bugId; + + public Spec() { + super(XMLNAME); + setXMLNames(NAMES); + } + + public void setSuiteDir(File suiteDir) { + this.suiteDir = suiteDir; + } + + public File getSuiteDir() { + return suiteDir; + } + + /** @param bugId 100..9999 */ + public void setBugId(int bugId) { + LangUtil.throwIaxIfFalse((bugId > 10) && (bugId < 10000), "bad bug id: " + bugId); + this.bugId = bugId; + } + + public int getBugId() { + return bugId; + } + + public void setTestDirOffset(String testDirOffset) { + if (!LangUtil.isEmpty(testDirOffset)) { + this.testDirOffset = testDirOffset; + } + } + + public String getTestDirOffset() { + return (null == testDirOffset ? "" : testDirOffset); + } + + /** + * @param sandbox ignored + * @see org.aspectj.testing.harness.bridge.AbstractRunSpec#makeAjcRun(Sandbox, Validator) + */ + public IRunIterator makeRunIterator(Sandbox sandbox, Validator validator) { + LangUtil.throwIaxIfNull(validator, "validator"); + + // if no one set suiteDir, see if we have a source location + if (null == suiteDir) { + ISourceLocation loc = getSourceLocation(); + if (!validator.nullcheck(loc, "suite file location") + || !validator.nullcheck(loc.getSourceFile(), "suite file")) { + return null; + } + File locDir = loc.getSourceFile().getParentFile(); + if (!validator.canReadDir(locDir, "source location dir")) { + return null; + } + suiteDir = locDir; + } + + // we make a new sandbox with more state for our subruns, keep that, + // in order to defer initialization to nextRun() + File testBaseDir; + String testDirOffset = getTestDirOffset(); + if (LangUtil.isEmpty(testDirOffset)) { + testBaseDir = suiteDir; + } else { + testBaseDir = new File(suiteDir, testDirOffset); + if (!validator.canReadDir(testBaseDir, "testBaseDir")) { + return null; + } + } + Sandbox childSandbox = null; + try { + childSandbox = new Sandbox(testBaseDir, validator); + validator.registerSandbox(childSandbox); + } catch (IllegalArgumentException e) { + validator.fail(e.getMessage()); + return null; + } + return new AjcTest(this, childSandbox, validator); + } + + /** @see IXmlWritable#writeXml(XMLWriter) */ + public void writeXml(XMLWriter out) { + out.println(""); + String value = (null == testDirOffset? "" : testDirOffset); + String attr = out.makeAttribute("dir", value); + if (0 != bugId) { + attr += " " + out.makeAttribute("pr", ""+bugId); + } + out.startElement(xmlElementName, attr, false); + super.writeAttributes(out); + out.endAttributes(); + super.writeChildren(out); + out.endElement(xmlElementName); + } + + /** + * AjcTest overrides this to skip if + *
      + *
    • the spec has a keyword the parent wants to skip
    • + *
    • the spec does not have keyword the parent requires
    • + *
    • the spec does not have the bugId required
    • + *
    + * @return false if this wants to be skipped, true otherwise + * @throws Error if selected option is not of the form + *
    -ajctest[Require|Skip]Keywords=keyword{,keyword}..
    . + */ + protected boolean doAdoptParentValues(RT parentRuntime, IMessageHandler handler) { + if (!super.doAdoptParentValues(parentRuntime, handler)) { + return false; + } + runtime.copy(parentRuntime); + + String[] globalOptions = runtime.extractOptions(VALID_OPTIONS, true); + for (int i = 0; i < globalOptions.length; i++) { + String option = globalOptions[i]; + if (!option.startsWith(OPTION_PREFIX)) { + throw new Error("only expecting " + OPTION_PREFIX + "..: " + option); + } + option = option.substring(OPTION_PREFIX.length()); + boolean keywordMustExist = false; + String havePr = null; + if (option.startsWith(REQUIRE_KEYWORDS)) { + option = option.substring(REQUIRE_KEYWORDS.length()); + keywordMustExist = true; + } else if (option.startsWith(SKIP_KEYWORDS)) { + option = option.substring(SKIP_KEYWORDS.length()); + } else if (option.startsWith(PICK_PR)) { + if (0 == bugId) { + skipMessage(handler, "bugId required, but no bugId for this test"); + return false; + } else { + havePr = "" + bugId; + } + option = option.substring(PICK_PR.length()); + } else { + throw new Error("unrecognized suffix: " + globalOptions[i] + + " (expecting: " + OPTION_PREFIX + VALID_SUFFIXES + "...)"); + } + List specs = LangUtil.commaSplit(option); + // XXX also throw Error on empty specs... + for (Iterator iter = specs.iterator(); iter.hasNext();) { + String spec = (String) iter.next(); + if (null != havePr) { + if (havePr.equals(spec)) { // String.equals() + return true; + } + } else if (keywordMustExist != keywords.contains(spec)) { + String reason = "keyword " + spec + + " was " + (keywordMustExist ? "not found" : "found"); + skipMessage(handler, reason); + return false; + } + } + if (null != havePr) { + skipMessage(handler, "bugId required, but not matched for this test"); + return false; + } + } + return true; + } + + } // AjcTest.Spec + + /** + * A suite of AjcTest has children for each AjcTest + * and flows all options down as globals + */ + public static class Suite extends RunSpecIterator { + final Spec spec; + public Suite(Spec spec, Sandbox sandbox, Validator validator) { + super(spec, sandbox, validator, false); + this.spec = spec; + } + + /** + * While being called to make the sandbox for the child, + * set up the child's suite dir based on ours. + * @param child must be instanceof AjcTest.Spec + * @see org.aspectj.testing.harness.bridge.RunSpecIterator#makeSandbox(IRunSpec, Validator) + * @return super.makeSandbox(child, validator) + */ + protected Sandbox makeSandbox( + IRunSpec child, + Validator validator) { + if (!(child instanceof AjcTest.Spec)) { + validator.fail("only expecting AjcTest children"); + return null; + } + if (!validator.canReadDir(spec.suiteDir, "spec.suiteDir")) { + return null; + } + ((AjcTest.Spec) child).setSuiteDir(spec.suiteDir); + return super.makeSandbox(child, validator); + } + + /** + * A suite spec contains AjcTest children. + * The suite dir or source location should be set + * if the tests do not each have a source location + * with a source file in the suite dir. + * XXX whether to write out suiteDir in XML? + */ + public static class Spec extends AbstractRunSpec { + public static final String XMLNAME = "suite"; + /** + * do description, do sourceLocation, + * do keywords, do options, skip paths, do comment + * skip dirChanges, skip messages and do children + * (though we do children directly). + */ + private static final XMLNames NAMES = new XMLNames(XMLNames.DEFAULT, + null, null, null, null, "", null, true, true, false); + File suiteDir; + public Spec() { + super(XMLNAME, false); // do not skip this even if children skip + } + + /** @param suiteDirPath the String path to the base suite dir */ + public void setSuiteDir(String suiteDirPath) { + if (!LangUtil.isEmpty(suiteDirPath)) { + this.suiteDir = new File(suiteDirPath); + } + } + + /** @param suiteDirFile the File for the base suite dir */ + public void setSuiteDirFile(File suiteDir) { + this.suiteDir = suiteDir; + } + + /** @get suiteDir from any set or source location if set */ + public File getSuiteDirFile() { + if (null == suiteDir) { + ISourceLocation loc = getSourceLocation(); + if (null != loc) { + File sourceFile = loc.getSourceFile(); + if (null != sourceFile) { + suiteDir = sourceFile.getParentFile(); + } + } + } + return suiteDir; + } + + /** + * @return + * @see org.aspectj.testing.harness.bridge.AbstractRunSpec#makeRunIterator(Sandbox, Validator) + */ + public IRunIterator makeRunIterator( + Sandbox sandbox, + Validator validator) { + return new Suite(this, sandbox, validator); + } + + public String toString() { + // removed nKids as misleading, since children.size() may change + //int nKids = children.size(); + //return "Suite.Spec(" + suiteDir + ", " + nKids + " tests)"; + return "Suite.Spec(" + suiteDir + ")"; + } + } + } +} diff --git a/testing/src/org/aspectj/testing/harness/bridge/CompilerRun.java b/testing/src/org/aspectj/testing/harness/bridge/CompilerRun.java new file mode 100644 index 000000000..ccb38b2f4 --- /dev/null +++ b/testing/src/org/aspectj/testing/harness/bridge/CompilerRun.java @@ -0,0 +1,966 @@ +/* ******************************************************************* + * Copyright (c) 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.harness.bridge; + +import org.aspectj.bridge.ICommand; +import org.aspectj.bridge.IMessage; +import org.aspectj.bridge.IMessageHandler; +import org.aspectj.bridge.MessageUtil; +import org.aspectj.bridge.ReflectionFactory; +import org.aspectj.testing.run.IRunIterator; +import org.aspectj.testing.run.IRunStatus; +import org.aspectj.testing.run.WrappedRunIterator; +import org.aspectj.testing.xml.SoftMessage; +import org.aspectj.testing.xml.XMLWriter; +import org.aspectj.util.FileUtil; +import org.aspectj.util.LangUtil; + +import java.io.File; +import java.io.IOException; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +/** + * Run the compiler once in non-incremental mode. + */ +public class CompilerRun implements IAjcRun { + static final String[] RA_String = new String[0]; + + static final String[] JAR_SUFFIXES = new String[] { ".jar", ".zip" }; + + static final String[] SOURCE_SUFFIXES + = (String[]) FileUtil.SOURCE_SUFFIXES.toArray(new String[0]); + + /** specifications, set on construction */ + Spec spec; + + //------------ calculated during setup + /** get shared stuff during setup */ + Sandbox sandbox; + + /** + * During run, these String are passed as the source and arg files to compile. + * The list is set up in setupAjcRun(..), when arg files are prefixed with "@". + */ + final List /*String*/ arguments; + + /** + * During run, these String are collapsed and passed as the injar option. + * The list is set up in setupAjcRun(..). + */ + final List /*String*/ injars; + + /** + * During run, these String are collapsed and passed as the aspectpath option. + * The list is set up in setupAjcRun(..). + */ + final List /*String*/ aspectpath; + + private CompilerRun(Spec spec) { + if (null == spec) { + throw new IllegalArgumentException("null spec"); + } + this.spec = spec; + arguments = new ArrayList(); + injars = new ArrayList(); + aspectpath = new ArrayList(); + } + + /** + * This checks that the spec is reasonable and does setup: + *
  • calculate and set sandbox testBaseSrcDir as + * {Sandbox.testBaseDir}/{Spec.testSrcDirOffset}/
  • + *
  • the list of source File to compile as + * {Sandbox.testBaseSrcDir}/{Spec.getPaths..}
  • + * All sources must be readable at this time. + * @param classesDir the File + * @see org.aspectj.testing.harness.bridge.AjcTest.IAjcRun#setup(File, File) + * @throws AbortException containing IOException or IllegalArgumentException + * if the staging operations fail + */ + public boolean setupAjcRun(Sandbox sandbox, Validator validator) { + + if (!validator.nullcheck(spec.getOptionsArray(), "localOptions") + || !validator.nullcheck(sandbox, "sandbox") + || !validator.nullcheck(spec.compiler, "compilerName") + || !validator.canRead(Globals.F_aspectjrt_jar, "aspectjrt.jar") + || !validator.canRead(Globals.F_testingclient_jar, "testing-client.jar") + //|| !validator.canRead(Main.F_bridge_jar, "bridge.jar") + ) { + return false; + } + + this.sandbox = sandbox; + + File[] cp = new File[] + { sandbox.classesDir, Globals.F_aspectjrt_jar, Globals.F_testingclient_jar }; + sandbox.setClasspath(cp, true, this); + + String rdir = spec.testSrcDirOffset; + File testBaseSrcDir; + if ((null == rdir) || (0 == rdir.length())) { + testBaseSrcDir = sandbox.testBaseDir; + } else { + testBaseSrcDir = new File(sandbox.testBaseDir, rdir); + if (!validator.canReadDir(testBaseSrcDir, "sandbox.testBaseSrcDir")) { + return false; + } + } + sandbox.setTestBaseSrcDir(testBaseSrcDir, this); + + arguments.clear(); + + // sources come as relative paths - check read, copy if staging + + // this renders paths absolute before run(RunStatusI) is called + // for a compile run to support relative paths + source base + // change so the run calculates the paths (differently when staging) + + final String[] injarPaths; + final String[] srcPaths; + { + final String[] paths = spec.getPathsArray(); + srcPaths = LangUtil.endsWith(paths, CompilerRun.SOURCE_SUFFIXES, true); + injarPaths = LangUtil.endsWith(paths, CompilerRun.JAR_SUFFIXES, true); + } + // validate readable for sources + if (!validator.canRead(testBaseSrcDir, srcPaths, "sources") + || !validator.canRead(testBaseSrcDir, injarPaths, "injars") + || !validator.canRead(testBaseSrcDir, spec.argfiles, "argfiles")) { + return false; + } + int numSources = srcPaths.length + injarPaths.length + spec.argfiles.length; + if (numSources < 1) { + validator.fail("no source files, input jars, or arg files"); + return false; + } + + File[] srcFiles; + File[] argFiles; + File[] injarFiles; + File[] aspectFiles; + if (!spec.isStaging()) { // XXX why this? was always? || (testBaseSrcDir != sandbox.stagingDir))) { + srcFiles = FileUtil.getBaseDirFiles(testBaseSrcDir, srcPaths, CompilerRun.SOURCE_SUFFIXES); + argFiles = FileUtil.getBaseDirFiles(testBaseSrcDir, spec.argfiles); + injarFiles = FileUtil.getBaseDirFiles(testBaseSrcDir, injarPaths); + aspectFiles = FileUtil.getBaseDirFiles(testBaseSrcDir, spec.aspectpath); + } else { // staging - copy files + try { + srcFiles = FileUtil.copyFiles(testBaseSrcDir, srcPaths, sandbox.stagingDir); + argFiles = FileUtil.copyFiles(testBaseSrcDir, spec.argfiles, sandbox.stagingDir); + injarFiles = FileUtil.copyFiles(testBaseSrcDir, injarPaths, sandbox.stagingDir); + aspectFiles = FileUtil.copyFiles(testBaseSrcDir, spec.aspectpath, sandbox.stagingDir); + } catch (IllegalArgumentException e) { + validator.fail("staging - bad input", e); + return false; + } catch (IOException e) { + validator.fail("staging - operations", e); + return false; + } + // validate readable for copied sources + if (!validator.canRead(srcFiles, "copied paths") + || !validator.canRead(argFiles, "copied argfiles")) { + return false; + } + } + arguments.clear(); + injars.clear(); + aspectpath.clear(); + if (!LangUtil.isEmpty(srcFiles)) { + arguments.addAll(Arrays.asList(FileUtil.getPaths(srcFiles))); + } + if (!LangUtil.isEmpty(injarFiles)) { + injars.addAll(Arrays.asList(FileUtil.getPaths(injarFiles))); + } + if (!LangUtil.isEmpty(aspectFiles)) { + aspectpath.addAll(Arrays.asList(FileUtil.getPaths(aspectFiles))); + } + if (!LangUtil.isEmpty(argFiles)) { + String[] ra = FileUtil.getPaths(argFiles); + for (int j = 0; j < ra.length; j++) { + arguments.add("@" + ra[j]); + } + if (spec.isStaging) { + validator.info("warning: files listed in argfiles not staged"); + } + } + + return true; + } +// if ((null != spec.dirChanges) && (0 < spec.numMessages(IMessage.ERROR, false))) { +// validator.info("CompilerRun warning: when expecting errors, cannot expect dir changes"); +// } + + + /** + * Setup result evaluation and command line, run, and evaluate result. + *
  • setup an AjcMessageHandler using the expected messages from + * {@link Spec#getMessages()}.
  • + *
  • heed any globals interpreted into a TestSetup by reading + * {@link Spec@getOptions()}. For a list of supported globals, see + * {@link setupArgs(ArrayList, IMessageHandler}.
  • + *
  • construct a command line, using as classpath + * {@link Sandbox.classpathToString()}
  • + *
  • construct a compiler using {@link Spec#compilerName} + * or any overriding value set in TestSetup.
  • + *
  • Just before running, set the compiler in the sandbox using + * {@link Sandbox.setCompiler(ICommand)}.
  • + *
  • After running, report AjcMessageHandler results to the status parameter. + * If the AjcMessageHandler reports a failure, then send info messages + * for the Spec, TestSetup, and command line.
  • + * XXX better to upgrade AjcMessageHandler to adopt status + * so the caller can control fast-fail, etc. + * @see org.aspectj.testing.run.IRun#run(IRunStatus) + */ + public boolean run(IRunStatus status) { + if (null == spec.testSetup) { + MessageUtil.abort(status, "no test setup - adoptParentValues not called"); + return false; + } else if (!spec.testSetup.result) { + MessageUtil.abort(status, spec.testSetup.failureReason); + return false; + } + AjcMessageHandler handler = new AjcMessageHandler(spec.getMessages()); + handler.init(); + boolean handlerResult = false; + boolean result = false; + boolean commandResult = false; + ArrayList argList = new ArrayList(); + final Spec.TestSetup setupResult = spec.testSetup; + try { + argList.addAll(setupResult.commandOptions); + argList.add("-d"); + String outputDirPath = sandbox.classesDir.getAbsolutePath(); + try { // worth it to try for canonical? + outputDirPath = sandbox.classesDir.getCanonicalPath(); + } catch (IOException e) { + MessageUtil.abort(status, "canonical " + sandbox.classesDir, e); + } + argList.add(outputDirPath); + argList.add("-classpath"); + argList.add(sandbox.classpathToString(this)); + // XXX classpath additions here? + + if (0 < injars.size()) { + argList.add("-injars"); + argList.add(FileUtil.flatten((String[]) injars.toArray(new String[0]), null)); + } + + if (0 < aspectpath.size()) { + argList.add("-aspectpath"); + argList.add(FileUtil.flatten((String[]) aspectpath.toArray(new String[0]), null)); + } + + // add both java/aspectj and argfiles + argList.addAll(arguments); + + // hack - do seeking on request as a side effect. reimplement as listener + if (null != setupResult.seek) { + String slopPrefix = Spec.SEEK_MESSAGE_PREFIX + " slop - "; + PrintStream slop = MessageUtil.handlerPrintStream( + status, + IMessage.INFO, + System.err, + slopPrefix); + List found = FileUtil.lineSeek(setupResult.seek, arguments, false, slop); + if (!LangUtil.isEmpty(found)) { + for (Iterator iter = found.iterator(); iter.hasNext();) { + MessageUtil.info(status, Spec.SEEK_MESSAGE_PREFIX + iter.next()); + } + } + } + + ICommand compiler = ReflectionFactory.makeCommand(setupResult.compilerName, status); + DirChanges dirChanges = null; + if (null == compiler) { + MessageUtil.fail(status, "unable to make compiler " + setupResult.compilerName); + return false; + } else { + if (status.aborted()) { + MessageUtil.debug(status, "aborted, but compiler valid?: " + compiler); + } else { + // same DirChanges handling for JavaRun, CompilerRun, IncCompilerRun + // XXX around advice or template method/class + if (!LangUtil.isEmpty(spec.dirChanges)) { + LangUtil.throwIaxIfFalse(1 == spec.dirChanges.size(), "expecting 0..1 dirChanges"); + dirChanges = new DirChanges((DirChanges.Spec) spec.dirChanges.get(0)); + if (!dirChanges.start(status, sandbox.classesDir)) { + return false; // setup failed + } + } + MessageUtil.info(status, compiler + "(" + argList + ")"); + sandbox.setCommand(compiler, this); + String[] args = (String[]) argList.toArray(RA_String); + commandResult = compiler.runCommand(args, handler); + } + } + if (!setupResult.ignoreWarningsSet) { + handlerResult = handler.passed(); + } else { + handlerResult = handler.passed(setupResult.ignoreWarnings); + } + if (!handlerResult) { + return false; + } else { + result = (commandResult == handler.expectingCommandTrue()); + if (! result) { + String m = commandResult + ? "compile did not fail as expected" + : "compile failed unexpectedly"; + MessageUtil.fail(status, m); + } else if (null != dirChanges) { + result = dirChanges.end(status, sandbox.testBaseDir); + } + } + return result; + } finally { + if (!handlerResult) { // more debugging context in case of failure + MessageUtil.info(handler, spec.toLongString()); + MessageUtil.info(handler, "" + argList); + if (null != setupResult) { + MessageUtil.info(handler, "" + setupResult); + } + } + handler.report(status); // XXX weak - actual messages not reported in real-time, no fast-fail + } + } + + + public String toString() { + return "CompilerRun(" + spec + ")"; + } +// String[] sourcePaths = (null == this.sourcePaths ? new String[0] : this.sourcePaths); +// List sources = (null == this.sources ? Collections.EMPTY_LIST : this.sources); +// return "CompilerRun-" + compilerName +// + "(" + Arrays.asList(sourcePaths) +// + ", " + sources +// + ", " + Arrays.asList(globalOptions) +// + ", " + Arrays.asList(localOptions) +// + ")"; + + /** + * Initializer/factory for CompilerRun + * any path or file is relative to this test base dir + */ + public static class Spec extends AbstractRunSpec { + public static final String XMLNAME = "compile"; + static final String SEEK_PREFIX = "-seek:"; + static final String SEEK_MESSAGE_PREFIX = "found: "; + + /** no support in the harness for these otherwise-valid options */ + private static final String[] INVALID_OPTIONS = new String[] + { "-workingdir", "-argfile", "-sourceroot", "-outjar"}; + // when updating these, update tests/harness/selectionTest.xml + + /** no support in the eclipse-based compiler for these otherwise-valid options */ + private static final String[] INVALID_ECLIPSE_OPTIONS = new String[] + { "-lenient", "-strict", "-usejavac", "-preprocess", + "-XOcodeSize", "-XSerializable", "-XaddSafePrefix", + "-XserializableAspects", "-XtargetNearSource" }; + + /** options supported by the harness */ + private static final String[] VALID_OPTIONS = new String[] + { + SEEK_PREFIX, + // eajc does not support -usejavac, -preprocess + // testFlag() handles -ajc, -eclipse, -ignoreWarnings + "-usejavac", "-preprocess", + "-Xlint", "-lenient", "-strict", + "-source14", "-verbose", "-emacssym", + "-ajc", "-eclipse", "-ignoreWarnings", + + "!usejavac", "!preprocess", + "!Xlint", "!lenient", "!strict", + "!source14", "!verbose", "!emacssym", + "!ajc", "!eclipse", + + "^usejavac", "^preprocess", + "^Xlint", "^lenient", "^strict", + "^source14", "^verbose", "^emacssym", + "^ajc", "^eclipse" + }; + public static final String DEFAULT_COMPILER + = ReflectionFactory.ECLIPSE; +// = ReflectionFactory.OLD_AJC; + /** + * Retitle description to title, paths to files, do comment + * do dirChanges, and print no chidren. + */ + private static final XMLNames NAMES = new XMLNames(XMLNames.DEFAULT, + "title", null, null, null, "files", null, false, false, true); + + protected String compiler; + + protected TestSetup testSetup; + + protected String[] argfiles = new String[0]; + protected String[] aspectpath = new String[0]; + + /** src path = {suiteParentDir}/{testBaseDirOffset}/{testSrcDirOffset}/{path} */ + protected String testSrcDirOffset; + + public Spec() { + super(XMLNAME); + setXMLNames(NAMES); + compiler = DEFAULT_COMPILER; + } + + public void setCompiler(String compilerName) { + this.compiler = compilerName; + } + + public void setTestSrcDirOffset(String s) { + if (null != s) { + testSrcDirOffset = s; + } + } + + /** override to set dirToken to Sandbox.CLASSES and default suffix to ".class" */ + public void addDirChanges(DirChanges.Spec spec) { + if (null == spec) { + return; + } + spec.setDirToken(Sandbox.CLASSES_DIR); + spec.setDefaultSuffix(".class"); + super.addDirChanges(spec); + } + protected String getPrintName() { + return "CompilerRun.Spec " + getShortCompilerName(); + } + + public String toLongString() { + return getPrintName() + "(" + super.containedSummary() + ")"; + } + + public String toString() { + return getPrintName() + "(" + super.containedSummary() + ")"; + } + + /** bean mapping for writers */ + public void setFiles(String paths) { + addPaths(paths); + } + + /** + * Set aspectpath, deleting any old ones + * @param files comma-delimited list of argfiles - ignored if null or empty + */ + public void setAspectpath(String files) { + if (!LangUtil.isEmpty(files)) { + aspectpath = XMLWriter.unflattenList(files); + } + } + + /** + * Set argfiles, deleting any old ones + * @param files comma-delimited list of argfiles - ignored if null or empty + */ + public void setArgfiles(String files) { + if (!LangUtil.isEmpty(files)) { + argfiles = XMLWriter.unflattenList(files); + } + } + + /** @return String[] copy of argfiles array */ + public String[] getArgfilesArray() { + String[] argfiles = this.argfiles; + if (LangUtil.isEmpty(argfiles)) { + return new String[0]; + } + return (String[]) LangUtil.copy(argfiles); + } + + /** + * This implementation skips if: + *
      + *
    • incremental test, but using ajc (not eclipse)
    • + *
    • usejavac, but javac is not available on the classpath
    • + *
    • eclipse, but -usejavac or -preprocess test
    • + *
    • -source14, but running under 1.2 (XXX design)
    • + *
    • local/global option conflicts (-lenient/-strict)
    • + *
    • semantic conflicts (e.g., -lenient/-strict)
    • + *
    + * @return false if this wants to be skipped, true otherwise + */ + protected boolean doAdoptParentValues(RT parentRuntime, IMessageHandler handler) { + if (!super.doAdoptParentValues(parentRuntime, handler)) { + return false; + } + testSetup = setupArgs(handler); + if (!testSetup.result) { + skipMessage(handler, testSetup.failureReason); + } + return testSetup.result; + } + + + private String getShortCompilerName() { + String cname = compiler; + if (null != testSetup) { + cname = testSetup.compilerName; + } + if (null != cname) { + int loc = cname.lastIndexOf("."); + if (-1 != loc) { + cname = cname.substring(loc+1); + } + } + return cname; + } + + /** @return a CompilerRun with this as spec if setup completes successfully. */ + public IRunIterator makeRunIterator(Sandbox sandbox, Validator validator) { + CompilerRun run = new CompilerRun(this); + if (run.setupAjcRun(sandbox, validator)) { + // XXX need name for compilerRun + return new WrappedRunIterator(this, run); + } + return null; + } + + /** + * Each non-incremental run, fold the global flags in with + * the run flags, which may involve adding or removing from + * either list, depending on the flag prefix: + *
      + *
    • -foo: use -foo unless forced off.
    • + *
    • ^foo: (force off) remove any -foo option from the run flags
    • + *
    • !foo: (force on) require the -foo flag
    • + *
    + * If there is a force conflict, then the test is skipped + * ("skipping" info message, TestSetup.result is false). + * This means an option local to the test which was specified + * without forcing may be overridden by a globally-forced option. + *

    + * There are some flags which are interpreted by the test + * and removed from the list of flags passed to the command + * (see testFlag()): + *

      + *
    • eclipse: use the new eclipse compiler (can force)
    • + *
    • ajc: use the old ajc compiler (can force)
    • + *
    • ignoreWarnings: ignore warnings in result evaluations (no force)
    • + *
    + *

    + * There are some flags which are inconsistent with each other. + * These are treated as conflicts and the test is skipped: + *

      + *
    • lenient, strict
    • + *
    + *

    + * The -source 1.4 flag should always be specified as -source14, + * as this will otherwise fail to process it correctly. + * This converts it back to -source 1.4. + *

    + * Finally, compiler limitations are enforced here by skipping + * tests which the compiler cannot do: + *

      + *
    • eclipse does not do -lenient, -strict, -usejavac, -preprocess, + * -XOcodeSize, -XSerializable, XaddSafePrefix, + * -XserializableAspects,-XtargetNearSource
    • + *
    • ajc does not run in incremental (staging) mode, + * nor with -usejavac if javac is not on the classpath
    • + *
    + * Errors:This will remove an arg not prefixed by [-|!|^] after + * providing an info message. + * TestSetup Result: + * If this completes successfully, then TestSetup.result is true, + * and commandOptions is not null, and any test flags (ignore warning, + * compiler) are reflected in the TestSetup. + * If this fails, then TestSetup.result is false, + * and a TestSetup.failreason is set. + * @return TestSetup with results + * (TestSetup result=false if the run should not continue) + */ + protected TestSetup setupArgs(IMessageHandler handler) { + // warning: HarnessSelectionTest checks for specific error wording + ArrayList argList = new ArrayList(); + argList.addAll(getOptionsList()); + final Spec spec = this; + TestSetup result = new TestSetup(); + if (argList.contains("-source")) { + result.failureReason = "use -source14 for -source 1.4: " + argList; + return result; + } + result.compilerName = spec.compiler; + String[] globalOptions = spec.runtime.extractOptions(Spec.VALID_OPTIONS, true); + if ((null != globalOptions) && (globalOptions.length > 0)) { + // --- fold in globals, removing conflicts, etc. + for (int i = 0; i < globalOptions.length; i++) { + String globalArg = globalOptions[i]; + if ((null == globalArg) || (2 > globalArg.length())) { + continue; + } else if (globalArg.startsWith(SEEK_PREFIX)) { + result.seek = globalArg.substring(SEEK_PREFIX.length()); + continue; + } else if ("-source".equals(globalArg)) { + result.failureReason = "use -source14 for -source 1.4 [" + i + "]"; + return result; + } + char first = globalArg.charAt(0); + globalArg = globalArg.substring(1); + boolean globalForceOn = (first == '!'); + boolean globalForceOff = (first == '^'); + boolean globalSet = (first == '-'); + if (!globalSet && !globalForceOn && !globalForceOff) { + MessageUtil.info(handler, "ignoring bad global: " + globalOptions[i]); + continue; + } + int argIndex = indexOf(globalArg, argList); + if (-1 == argIndex) { // no apparent conflict - look for eclipse/ajc conflicts XXX unresolved + boolean ajcGlobal = true; + if ("ajc".equals(globalArg)) { + argIndex = indexOf("eclipse", argList); + } else if ("eclipse".equals(globalArg)) { + argIndex = indexOf("ajc", argList); + ajcGlobal = false; + } + if (-1 != argIndex) { // resolve eclipse/ajc conflict + String arg = ((String) argList.get(argIndex)); + char argFirst = arg.charAt(0); + argList.remove(arg); // replace with resolved variant... + char ajcFirst; + char eclipseFirst; + if (ajcGlobal) { + ajcFirst = first; + eclipseFirst = argFirst; + } else { + ajcFirst = argFirst; + eclipseFirst = first; + } + if ('!' == eclipseFirst) { + if ('!' == ajcFirst) { + result.failureReason = "conflict between !eclipse and !ajc"; + return result; + } else { + argList.add("-eclipse"); + } + } else if (('!' == ajcFirst) || ('^' == eclipseFirst)) { + argList.add("-ajc"); + } else if ('^' == ajcFirst) { + argList.add("-eclipse"); + } else if (('-' != ajcFirst) || ('-' != eclipseFirst)) { + result.failureReason = "harness logic error resolving " + + arg + " and global " + globalArg; + return result; + } else if (ajcGlobal) { + argList.add("-ajc"); + } else { + argList.add("-eclipse"); + } + continue; // resolved + } + } + + if (-1 == argIndex) { // no dup, so no conflict + if (!globalForceOff) { + argList.add("-" + globalArg); + } + } else { // have conflict - resolve + String arg = (String) argList.get(argIndex); + first = arg.charAt(0); + boolean localForceOn = (first == '!'); + boolean localForceOff = (first == '^'); + boolean localSet = (first == '-'); + if (!localSet && !localForceOn && !localForceOff) { + result.failureReason = "only handling [-^!]{arg}: " + arg; + return result; + } + if ((localForceOn && globalForceOff) + || (localForceOff && globalForceOn)) { + result.failureReason = "force conflict between arg=" + + arg + " and global=" + globalOptions[i]; + return result; + } + if (globalForceOn) { + if (localForceOn) { // localSet is already correct, localForceOff was conflict + argList.remove(arg); // no !funkiness + argList.add("-" + globalArg); + } + } else if (globalSet) { + if (localSet) { + // do nothing - already correct + } else if (localForceOn) { + argList.remove(arg); // no !funkiness + argList.add("-" + globalArg); + } + } else if (globalForceOff) { + argList.remove(arg); + } else { + throw new Error("illegal arg state?? : " + arg); + //MessageUtil.info(handler, "illegal arg state??: " + arg); + } + } + } + } + + // remove funky prefixes from remainder, fixup two-part flags + // and interpret special flags + boolean source14 = false; + ArrayList toAdd = new ArrayList(); + for (ListIterator iter = argList.listIterator(); iter.hasNext();) { + String arg = (String) iter.next(); + if (testFlag(arg, result)) { + iter.remove(); + continue; + } + char c = arg.charAt(0); + String rest = arg.substring(1); + if (c == '^') { + iter.remove(); + continue; + } + if (c == '!') { + iter.remove(); + if (!("source14".equals(rest))) { + toAdd.add("-" + rest); + } else { + source14 = true; + } + rest = null; + } else if ("source14".equals(rest)) { + iter.remove(); + source14 = true; + } + } + if (source14) { + // must run under 1.4 VM or (if ajc) set up bootclasspath + if (!LangUtil.supportsJava("1.4")) { + if (ReflectionFactory.ECLIPSE.equals(result.compilerName)) { + result.failureReason + = "eclipse must run under 1.4 to implement -source 1.4"; + return result; + } + + if (!FileUtil.canReadFile(Globals.J2SE14_RTJAR)) { + result.failureReason + = "unable to get 1.4 libraries to implement -source 1.4"; + return result; + } + toAdd.add("-bootclasspath"); + toAdd.add(Globals.J2SE14_RTJAR.getAbsolutePath()); + } + toAdd.add("-source"); + toAdd.add("1.4"); + } + argList.addAll(toAdd); + + // finally, check for semantic conflicts + String[] badOptions = LangUtil.selectOptions(argList, Spec.INVALID_OPTIONS); + if (!LangUtil.isEmpty(badOptions)) { + result.failureReason = "no support for (normally-valid) options " + + Arrays.asList(badOptions); + } else if (argList.contains("-lenient") && argList.contains("-strict")) { + result.failureReason = "semantic conflict -lenient | -strict"; + } else if (ReflectionFactory.OLD_AJC.equals(result.compilerName)) { + if (spec.isStaging) { + result.failureReason = "OLD_AJC does not do incremental compiles"; + } else if (argList.contains("-usejavac") && !haveJavac()) { + result.failureReason = "-usejavac but no javac on classpath"; + } else { + result.result = true; + } + } else if (!ReflectionFactory.ECLIPSE.equals(result.compilerName)) { + result.failureReason = "unrecognized compiler: " + result.compilerName; + } else { + badOptions = LangUtil.selectOptions(argList, Spec.INVALID_ECLIPSE_OPTIONS); + if (!LangUtil.isEmpty(badOptions)) { + result.failureReason = "no support in eclipse-based compiler" + + " for (normally-valid) options "+ Arrays.asList(badOptions); + } else { + result.result = true; + } + } + if (result.result) { + result.commandOptions = argList; + } + return result; + } + + /** @return true if javac is available on the classpath */ + private boolean haveJavac() { // XXX copy/paste from JavaCWrapper.java + Class compilerClass = null; + try { + compilerClass = Class.forName("com.sun.tools.javac.Main"); + } catch (ClassNotFoundException ce1) { + try { + compilerClass = Class.forName("sun.tools.javac.Main"); + } catch (ClassNotFoundException ce2) { + } + } + return (null != compilerClass); + } + + + /** + * Handle flags that are interpreted by the test rather than the + * underlying command. These flags are to be removed from the + * arg list. + * @return true if this is a flag to remove from the arg list + */ + protected boolean testFlag(String arg, TestSetup result) { + if ("-eclipse".equals(arg) || "!eclipse".equals(arg) || "^ajc".equals(arg)) { + result.compilerName = ReflectionFactory.ECLIPSE; + return true; + } else if ("-ajc".equals(arg) || "!ajc".equals(arg) || "^eclipse".equals(arg)) { + result.compilerName = ReflectionFactory.OLD_AJC; + return true; + } else if ("-ignoreWarnings".equals(arg)) { + result.ignoreWarnings = true; + result.ignoreWarningsSet = true; + return true; + } + return false; + } + + // XXX need keys, cache... + /** @return index of global in argList, ignoring first char */ + protected int indexOf(String global, ArrayList argList) { + int max = argList.size(); + for (int i = 0; i < max; i++) { + if (global.equals(((String) argList.get(i)).substring(1))) { + return i; + } + } + return -1; + } + + /** + * Write this out as a compile element as defined in + * AjcSpecXmlReader.DOCTYPE. + * @see AjcSpecXmlReader#DOCTYPE + * @see IXmlWritable#writeXml(XMLWriter) + */ + public void writeXml(XMLWriter out) { + // If our state is empty, we just delegate to super. + if (DEFAULT_COMPILER.equals(compiler) + && LangUtil.isEmpty(testSrcDirOffset) + && LangUtil.isEmpty(argfiles)) { + super.writeXml(out); + return; + } + String attr = ""; + out.startElement(xmlElementName, false); + if (!DEFAULT_COMPILER.equals(compiler)) { + out.printAttribute("compiler", compiler); + } + if (!LangUtil.isEmpty(testSrcDirOffset)) { + out.printAttribute("dir", testSrcDirOffset); + } + if (!LangUtil.isEmpty(argfiles)) { + out.printAttribute("argfiles", out.flattenFiles(argfiles)); + } + super.writeAttributes(out); + out.endAttributes(); + if (!LangUtil.isEmpty(dirChanges)) { + DirChanges.Spec.writeXml(out, dirChanges); + } + List messages = getMessages(); + if (!LangUtil.isEmpty(messages)) { + SoftMessage.writeXml(out, messages); + } + out.endElement(xmlElementName); + } + + /** + * Encapsulate the directives that can be set using + * global arguments supplied in {@link Spec.getOptions()}. + * This supports changing the compiler and ignoring warnings. + */ + class TestSetup { + /** null unless overriding the compiler to be used */ + String compilerName; + /** + * true if we should tell AjcMessageHandler whether + * to ignore warnings in its result evaluation + */ + boolean ignoreWarningsSet; + + /** if telling AjcMessageHandler, what we tell it */ + boolean ignoreWarnings; + + /** false if setup failed */ + boolean result; + + /** if setup failed, this has the reason why */ + String failureReason; + + /** beyond running test, also seek text in sources */ + String seek; + + /** if setup completed, this has the combined global/local options */ + ArrayList commandOptions; + + public String toString() { + return "TestSetup(" + + (null == compilerName ? "" : compilerName + " ") + + (!ignoreWarningsSet ? "" + : (ignoreWarnings ? "" : "do not ") + + "ignore warnings ") + + (result ? "" : "setup failed") + + ")"; + } + } + } // CompilerRun.Spec +} // CompilerRun + +// /** +// * Write this out as a compile element as defined in +// * AjcSpecXmlReader.DOCTYPE. +// * @see AjcSpecXmlReader#DOCTYPE +// * @see IXmlWritable#writeXml(XMLWriter) +// */ +// public void writeXml(XMLWriter out) { +// StringBuffer sb = new StringBuffer(); +// Spec spec = this; +// final String elementName = "compile"; +// List list = spec.getOptionsList(); +// String args = XMLWriter.flattenList(spec.getOptionsList()).trim(); +// String argsAttr = out.makeAttribute("options", args).trim(); +// String files = XMLWriter.flattenFiles(spec.getPathsArray()).trim(); +// String filesAttr = out.makeAttribute("files", files).trim(); +// List messages = spec.getMessages(); +// int nMessages = messages.size(); +// int both = argsAttr.length() + filesAttr.length(); +// final int MAX = 55; +// +// // tortured logic to make more readable XML... +// if ((both < MAX) || (0 == args.length())) { +// // if short enough, print entire or just start +// if (0 != args.length()) { +// filesAttr = argsAttr + " " + filesAttr; +// } +// if (0 == nMessages) { +// out.printElement(elementName, filesAttr); +// return; +// } else { +// out.startElement(elementName, filesAttr, true); +// } +// } else if (argsAttr.length() < filesAttr.length()) { +// out.startElement(elementName, argsAttr, false); +// out.printAttribute("files", files); +// out.endAttributes(); +// } else { +// out.startElement(elementName, filesAttr, false); +// out.printAttribute("options", args); +// out.endAttributes(); +// } +// if (0 < nMessages) { +// SoftMessage.writeXml(out, messages); +// } +// out.endElement(elementName); +// } + diff --git a/testing/src/org/aspectj/testing/harness/bridge/DirChanges.java b/testing/src/org/aspectj/testing/harness/bridge/DirChanges.java new file mode 100644 index 000000000..73b930d71 --- /dev/null +++ b/testing/src/org/aspectj/testing/harness/bridge/DirChanges.java @@ -0,0 +1,522 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.harness.bridge; + +import org.aspectj.bridge.IMessageHandler; +import org.aspectj.bridge.MessageUtil; +import org.aspectj.testing.util.TestUtil; +import org.aspectj.testing.xml.IXmlWritable; +import org.aspectj.testing.xml.XMLWriter; +import org.aspectj.util.FileUtil; +import org.aspectj.util.LangUtil; + +import java.io.File; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * Calculate changes in a directory tree. + * Usage: + *
      + *
    • Set up with any expected changes and/or an expected directory
    • + *
    • Set up with any file checker
    • + *
    • start(..) before changes. + * This issues messages for any removed files not found, + * which represent an error in the expected changes.
    • + *
    • Do whatever operations will change the directory
    • + *
    • end(..). + * This issues messages for any files not removed, added, or updated, + * and, if any checker was set, any checker messages for matching + * added or updated files
    • + *
    + */ +public class DirChanges { + + private static final boolean EXISTS = true; + + final Spec spec; + + /** start time, in milliseconds - valid only from start(..)..end(..) */ + long startTime; + + /** base directory of actual files - valid only from start(..)..end(..) */ + File baseDir; + + /** if set, this is run against any resulting existing files */ + IFileChecker fileChecker; + + /** handler valid from start..end of start(..) and end(..) methods */ + IMessageHandler handler; + + /** + * Constructor for DirChanges. + */ + public DirChanges(Spec spec) { + LangUtil.throwIaxIfNull(spec, "spec"); + this.spec = spec; + } + + /** + * Inspect the base dir, and issue any messages for + * removed files not present. + * @param baseDir File for a readable directory + * @return true if this started without sending messages + * for removed files not present. + */ + public boolean start(IMessageHandler handler, File baseDir) { + FileUtil.throwIaxUnlessCanReadDir(baseDir, "baseDir"); + final IMessageHandler oldHandler = this.handler; + this.handler = handler; + this.baseDir = baseDir; + startTime = 0l; + final boolean doCompare = false; + boolean result + = exists("at start, did not expect file to be added", !EXISTS, spec.added, doCompare); + result &= exists("at start, expected file to be unchanged", EXISTS, spec.unchanged, doCompare); + result &= exists("at start, expected file to be updated", EXISTS, spec.updated, doCompare); + result &= exists("at start, expected file to be removed", EXISTS, spec.removed, doCompare); + startTime = System.currentTimeMillis(); + this.handler = oldHandler; + return result; + } + + + /** + * Inspect the base dir, issue any messages for + * files not added, files not updated, and files not removed, + * and compare expected/actual files added or updated. + * @throws IllegalStateException if called before start(..) + */ + public boolean end(IMessageHandler handler, File srcBaseDir) { + FileUtil.throwIaxUnlessCanReadDir(baseDir, "baseDir"); + if (0l == startTime) { + throw new IllegalStateException("called before start"); + } + final IMessageHandler oldHandler = this.handler; + this.handler = handler; + final boolean doCompare = (null != fileChecker); + final boolean fastFail = spec.fastFail; + boolean result + = exists("at end, expected file was not added", EXISTS, spec.added, doCompare); + if (result || !fastFail) { + result &= exists("at end, expected file was not unchanged", EXISTS, spec.unchanged, doCompare, false); + } + if (result || !fastFail) { + result &= exists("at end, expected file was not updated", EXISTS, spec.updated, doCompare); + } + if (result || !fastFail) { + result &= exists("at end, file exists, was not removed", !EXISTS, spec.removed, doCompare); + } + if (result || !fastFail) { + result &= compareDir(srcBaseDir); + } + // XXX validate that unchanged mod-time did not change + + this.handler = oldHandler; + baseDir = null; + startTime = 0l; + return result; + } + + /** + * Verify that all files in any specified expected directory + * have matching files in the base directory, putting any messages + * in the handler (only one if the spec indicates fast-fail). + * @param srcBaseDir the File for the base directory of the test sources + * (any expected dir is specified relative to this directory) + * @return true if the same, false otherwise + */ + private boolean compareDir(File srcBaseDir) { + if (null == spec.dirExpected) { + return true; + } + File expDir = new File(srcBaseDir, spec.dirExpected); + File actDir = baseDir; + return TestUtil.sameDirectoryContents(handler, expDir, actDir, spec.fastFail); + } + + + /** @param comp FileMessageComparer (if any) given matching messages to compare */ + protected void setFileComparer(IFileChecker comp) { + this.fileChecker = comp; + } + + + + /** + * Signal fail if any files do {not} exist or do {not} have last-mod-time after startTime + * @param handler the IMessageHandler sink for messages + * @param label the String infix for the fail message + * @param exists if true, then file must exist and be modified after start time; + * if false, then the file must not exist or must be modified before start time. + * @param pathList the List of path (without any Spec.defaultSuffix) of File + * in Spec.baseDir to find (or not, if !exists) + */ + protected boolean exists( + String label, + boolean exists, + List pathList, + boolean doCompare) { + boolean expectStartEarlier = true; + return exists(label, exists, pathList,doCompare, true); + } + protected boolean exists( + String label, + boolean exists, + List pathList, + boolean doCompare, + boolean expectStartEarlier) { + boolean result = true; + if (!LangUtil.isEmpty(pathList)) { + final File expDir = ((!doCompare || (null == spec.dirExpected)) + ? null + : new File(baseDir, spec.dirExpected)); + for (Iterator iter = pathList.iterator(); iter.hasNext();) { + final String entry = (String) iter.next() ; + String path = entry ; + if (null != spec.defaultSuffix) { + if (".class".equals(spec.defaultSuffix)) { + path = path.replace('.', '/'); + } + path = path + spec.defaultSuffix; + } + File actualFile = new File(baseDir, path); + if (exists != (actualFile.canRead() && actualFile.isFile() + && (expectStartEarlier + ? startTime <= actualFile.lastModified() + : startTime > actualFile.lastModified() + ))) { + failMessage(handler, exists, label, path, actualFile); + if (result) { + result = false; + } + } else if (exists && doCompare && (null != fileChecker)) { + if (!fileChecker.checkFile(handler, path, actualFile) && result) { + result = false; + } + } + } + } + return result; + } + + /** + * Generate fail message "{un}expected {label} file {path} in {baseDir}". + * @param handler the IMessageHandler sink + * @param label String message infix + * @param path the path to the file + */ + protected void failMessage( + IMessageHandler handler, + boolean exists, + String label, + String path, + File file) { + MessageUtil.fail(handler, label + " \"" + path + "\" in " + baseDir); + } + + + /** Check actual File found at a path, usually to diff expected/actual contents */ + public static interface IFileChecker { + /** + * Check file found at path. + * Implementations should return false when adding fail (or worse) + * message to the handler, and true otherwise. + * @param handler IMessageHandler sink for messages, esp. fail. + * @param path String for normalized path portion of actualFile.getPath() + * @param actualFile File to file found + * @return false if check failed and messages added to handler + */ + boolean checkFile(IMessageHandler handler, String path, File actualFile); + } + +// /** +// * Default FileChecker compares files literally, transforming any +// * with registered normalizers. +// */ +// public static class FileChecker implements IFileChecker { +// final File baseExpectedDir; +// NormalizedCompareFiles fileComparer; +// +// public FileChecker(File baseExpectedDir) { +// this.baseExpectedDir = baseExpectedDir; +// fileComparer = new NormalizedCompareFiles(); +// } +// public boolean checkFile(IMessageHandler handler, String path, File actualFile) { +// if (null == baseExpectedDir) { +// MessageUtil.error(handler, "null baseExpectedDir set on construction"); +// } else if (!baseExpectedDir.canRead() || !baseExpectedDir.isDirectory()) { +// MessageUtil.error(handler, "bad baseExpectedDir: " + baseExpectedDir); +// } else { +// File expectedFile = new File(baseExpectedDir, path); +// if (!expectedFile.canRead()) { +// MessageUtil.fail(handler, "cannot read expected file: " + expectedFile); +// } else { +// return doCheckFile(handler, expectedFile, actualFile, path); +// } +// } +// return false; +// } +// +// protected boolean doCheckFile( +// IMessageHandler handler, +// File expectedFile, +// File actualFile, +// String path) { +// fileComparer.setHandler(handler); +// FileLine[] expected = fileComparer.diff(); +// return false; +// } +// } + +// /** +// * CompareFiles implementation that pre-processes input +// * to normalize it. Currently it reads all files except +// * .class files, which it disassembles first. +// */ +// public static class NormalizedCompareFiles extends CompareFiles { +// private final static String[] NO_PATHS = new String[0]; +// private static String normalPath(File file) { // XXX util +// if (null == file) { +// return ""; +// } +// return file.getAbsolutePath().replace('\\', '/'); +// } +// +// private String[] baseDirs; +// private IMessageHandler handler; +// +// public NormalizedCompareFiles() { +// } +// +// void init(IMessageHandler handler, File[] baseDirs) { +// this.handler = handler; +// if (null == baseDirs) { +// this.baseDirs = NO_PATHS; +// } else { +// this.baseDirs = new String[baseDirs.length]; +// for (int i = 0; i < baseDirs.length; i++) { +// this.baseDirs[i] = normalPath(baseDirs[i]) + "/"; +// } +// } +// } +// +// private String getClassName(File file) { +// String result = null; +// String path = normalPath(file); +// if (!path.endsWith(".class")) { +// MessageUtil.error(handler, +// "NormalizedCompareFiles expected " +// + file +// + " to end with .class"); +// } else { +// path = path.substring(0, path.length()-6); +// for (int i = 0; i < baseDirs.length; i++) { +// if (path.startsWith(baseDirs[i])) { +// return path.substring(baseDirs[i].length()).replace('/', '.'); +// } +// } +// MessageUtil.error(handler, +// "NormalizedCompareFiles expected " +// + file +// + " to start with one of " +// + LangUtil.arrayAsList(baseDirs)); +// } +// +// return result; +// } +// +// /** +// * Read file as normalized lines, sending handler any messages +// * ERROR for input failures and FAIL for processing failures. +// * @return NOLINES on error or normalized lines from file otherwise +// */ +// public FileLine[] getFileLines(File file) { +// FileLineator capture = new FileLineator(); +// InputStream in = null; +// try { +// if (!file.getPath().endsWith(".class")) { +// in = new FileInputStream(file); +// FileUtil.copyStream( +// new BufferedReader(new InputStreamReader(in)), +// new PrintWriter(capture)); +// } else { +// String name = getClassName(file); +// if (null == name) { +// return new FileLine[0]; +// } +// String path = normalPath(file); +// path = path.substring(0, path.length()-name.length()); +// // XXX sole dependency on bcweaver/bcel +// LazyClassGen.disassemble(path, name, capture); +// } +// } catch (IOException e) { +// MessageUtil.fail(handler, +// "NormalizedCompareFiles IOException reading " + file, e); +// return null; +// } finally { +// if (null != in) { +// try { in.close(); } +// catch (IOException e) {} // ignore +// } +// capture.flush(); +// capture.close(); +// } +// String missed = capture.getMissed(); +// if (!LangUtil.isEmpty(missed)) { +// MessageUtil.fail(handler, +// "NormalizedCompareFiles missed input: " +// + missed); +// return null; +// } else { +// return capture.getFileLines(); +// } +// } +// +// +// } + + /** + * Specification for a set of File added, removed, or updated + * in a given directory, or for a directory base for a tree of expected files. + * If defaultSuffix is specified, entries may be added without it. + */ + public static class Spec implements IXmlWritable { + public static final String XMLNAME = "dir-changes"; + String dirToken; + String defaultSuffix; + String dirExpected; + /** if true, ok to fail on first mis-match */ + boolean fastFail; + final ArrayList added; + final ArrayList removed; + final ArrayList updated; + final ArrayList unchanged; + + /** + * @param dirToken the symbol name of the base directory + * @param clipSuffix the String suffix, if any, to clip automatically + */ + public Spec() { + added = new ArrayList(); + removed = new ArrayList(); + updated = new ArrayList(); + unchanged = new ArrayList(); + } + + /** + * @param dirToken the symbol name of the base directory (classes, run) + */ + public void setDirToken(String dirToken) { + this.dirToken = dirToken; + } + + /** + * Set the directory containing the expected files. + * Currently this only is used to verify files that are expected + * and found after the process completes; it does not stand on its + * own as a specification of files. + * @param expectedDirRelativePath path relative to the test base + * of the directory containing expected results for the output dir. + */ + public void setExpDir(String expectedDirRelativePath) { + } + + /** + * @param clipSuffix the String suffix, if any, to clip automatically + */ + public void setDefaultSuffix(String defaultSuffix) { + this.defaultSuffix = defaultSuffix; + } + + public void setAdded(String items) { + XMLWriter.addFlattenedItems(added, items); + } + + public void setRemoved(String items) { + XMLWriter.addFlattenedItems(removed, items); + } + + public void setUpdated(String items) { + XMLWriter.addFlattenedItems(updated, items); + } + + public void setUnchanged(String items) { + XMLWriter.addFlattenedItems(unchanged, items); + } + public void setFastfail(boolean fastFail) { + this.fastFail = fastFail; // XXX support in XML + } + + /** @return true if some list was specified */ + private boolean hasFileList() { + return (!LangUtil.isEmpty(added) + || !LangUtil.isEmpty(removed) + || !LangUtil.isEmpty(updated) + || !LangUtil.isEmpty(unchanged) + ); + } + + /** this writes nothing if there are no added, removed, or changed */ + public void writeXml(XMLWriter out) { + if (!hasFileList()) { + return; + } + String attr = ""; + // XXX need to permit defaults here... + if (null != dirToken) { + attr = out.makeAttribute("dirToken", dirToken) + " "; + } + if (null != defaultSuffix) { + attr += out.makeAttribute("defaultSuffix", defaultSuffix); + } + out.startElement(XMLNAME, attr, false); + if (!LangUtil.isEmpty(added)) { + out.printAttribute("added", XMLWriter.flattenList(added)); + } + if (!LangUtil.isEmpty(removed)) { + out.printAttribute("removed", XMLWriter.flattenList(removed)); + } + if (!LangUtil.isEmpty(updated)) { + out.printAttribute("updated", XMLWriter.flattenList(updated)); + } + if (!LangUtil.isEmpty(unchanged)) { + out.printAttribute("unchanged", XMLWriter.flattenList(unchanged)); + } + out.endElement(XMLNAME); + } + + /** + * Write list as elements to XMLWriter. + * @param out XMLWriter output sink + * @param dirChanges List of DirChanges.Spec to write + */ + public static void writeXml(XMLWriter out, List dirChanges) { + if (LangUtil.isEmpty(dirChanges)) { + return; + } + LangUtil.throwIaxIfNull(out, "out"); + for (Iterator iter = dirChanges.iterator(); iter.hasNext();) { + DirChanges.Spec spec = (DirChanges.Spec) iter.next(); + if (null == spec) { + continue; + } + spec.writeXml(out); + } + } + +} // class Spec + +} diff --git a/testing/src/org/aspectj/testing/harness/bridge/FlatSuiteReader.java b/testing/src/org/aspectj/testing/harness/bridge/FlatSuiteReader.java new file mode 100644 index 000000000..dbf5dd259 --- /dev/null +++ b/testing/src/org/aspectj/testing/harness/bridge/FlatSuiteReader.java @@ -0,0 +1,377 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.harness.bridge; + +import org.aspectj.bridge.AbortException; +import org.aspectj.bridge.IMessage; +import org.aspectj.bridge.ISourceLocation; +import org.aspectj.bridge.Message; +import org.aspectj.bridge.SourceLocation; +import org.aspectj.bridge.IMessage.Kind; +import org.aspectj.testing.util.BridgeUtil; +import org.aspectj.testing.util.ObjectChecker; +import org.aspectj.testing.util.SFileReader; +import org.aspectj.testing.util.StandardObjectChecker; +import org.aspectj.util.FileUtil; +import org.aspectj.util.LangUtil; +import org.aspectj.util.LineReader; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +/** + * SFileReader.Maker implementation to read tests + * XXX supports iterative but not yet incremental compiles + */ +public class FlatSuiteReader implements SFileReader.Maker { + public static final String[] RA_String = new String[0]; + public static final FlatSuiteReader ME = new FlatSuiteReader(); + private static final SFileReader READER = new SFileReader(ME); + + static boolean isNumber(String s) { // XXX costly + if ((null == s) || (0 == s.length())) { + return false; + } + try { + Integer.valueOf(s); + return true; + } catch (NumberFormatException e) { + return false; + } + } + + /** if true, clean up records before returning from make */ + public boolean clean; + + private FlatSuiteReader() { + } + + /** + * @see org.aspectj.testing.harness.bridge.SFileReader.Maker#getType() + */ + public Class getType() { + return AjcTest.Spec.class; + } + + /** + * This constructs an AjcTest.Spec assuming we are at the start of a + * test definition in reader and taking the parent directory of + * the reader as the base directory for the test suite root. + * @return the next AjcTest in reader, or null + * @see org.aspectj.testing.harness.bridge.SFileReader.Maker#make(LineReader) + */ + public Object make(final LineReader reader) + throws AbortException, IOException { + final AjcTest.Spec result = new AjcTest.Spec(); + boolean usingEclipse = false; // XXX + /** handle read errors by throwing AbortException with context info */ + class R { + public String read(String context) throws IOException { + return read(context, true); + } + public String read(String context, boolean required) + throws IOException { + final boolean skipEmpties = false; + String result = reader.nextLine(skipEmpties); + if ((null != result) && (0 == result.length())) { + result = null; + } + if ((null == result) && required) { + String s = "expecting " + context + " at " + reader; + throw new AbortException(s); + } + return result; + } + } + + final R r = new R(); + //final String baseDir = reader.getFile().getParent(); + String line; + String[] words; + boolean isRequired = true; + + final int startLine = reader.getLineNumber() - 1; + + // description first - get from last line read + // XXX permits exactly one blank line between test records? + result.description = reader.lastLine(); + if (null == result.description) { + throw new AbortException("expecting description at " + reader); + } + + // next line is baseDir {option..} + line = r.read("baseDir {option..}"); + words = LangUtil.split(line); + if ((null == words) || (0 == words.length)) { + throw new AbortException( + "expecting dir {option..} at " + reader); + } + // XXX per-test (shared) root + //final File sourceRoot = new File(baseDir, words[0]); + result.setTestDirOffset(words[0]); + + String[] compileOptions = new String[words.length - 1]; + System.arraycopy(words, 1, compileOptions, 0, words.length - 1); + + // next are 1..n source lines: source... + CompilerRun.Spec lastCompileSpec = null; + // save last source file as default for error/warning line + File lastFile = null; // XXX per-compiler-run errors + while (null != (line = r.read("source.."))) { + words = LangUtil.split(line); + if (0 == FileUtil.sourceSuffixLength(words[0])) { // XXX + break; + } else { + lastCompileSpec = new CompilerRun.Spec(); + lastCompileSpec.testSrcDirOffset = null; + // srcs are in test base for old + lastCompileSpec.addOptions(compileOptions); + lastCompileSpec.addPaths(words); + lastFile = new File(words[words.length - 1]); + result.addChild(lastCompileSpec); + } + } + if (null == lastCompileSpec) { + throw new AbortException("expected sources at " + reader); + } + + ArrayList exp = new ArrayList(); + // !compile || noerrors || className {runOption..} + String first = words[0]; + if ("!compile".equals(first)) { + //result.className = words[0]; + //result.runOptions = new String[words.length-1]; + //System.arraycopy(words, 0, result.runOptions, 0, words.length-1); + } else if ("noerrors".equals(first)) { + // className is null, but no errors expected + // so compile succeeds but run not attempted + //result.errors = Main.RA_ErrorLine; + // result.runOptions = Main.RA_String; + } else if (isNumber(first) || (-1 != first.indexOf(":"))) { + exp.addAll(makeMessages(IMessage.ERROR, words, 0, lastFile)); + } else { + String[] args = new String[words.length - 1]; + System.arraycopy(words, 0, args, 0, args.length); + JavaRun.Spec spec = new JavaRun.Spec(); + spec.className = first; + spec.addOptions(args); + //XXXrun.runDir = sourceRoot; + result.addChild(spec); + } + + // optional: warnings, eclipse.warnings, eclipse.errors + // XXX unable to specify error in eclipse but not ajc + boolean gotErrors = false; + while (null + != (line = + r.read( + " errors, warnings, eclipse.warnings, eclipse.error", + false))) { + words = LangUtil.split(line); + first = words[0]; + if ("eclipse.warnings:".equals(first)) { + if (usingEclipse) { + exp.addAll( + makeMessages( + IMessage.WARNING, + words, + 0, + lastFile)); + } + } else if ("eclipse.errors:".equals(first)) { + if (usingEclipse) { + exp.addAll( + makeMessages(IMessage.ERROR, words, 0, lastFile)); + } + } else if ("warnings:".equals(first)) { + exp.addAll( + makeMessages(IMessage.WARNING, words, 0, lastFile)); + } else if (gotErrors) { + exp.addAll( + makeMessages(IMessage.WARNING, words, 0, lastFile)); + } else { + exp.addAll( + makeMessages(IMessage.ERROR, words, 0, lastFile)); + gotErrors = true; + } + } + lastCompileSpec.addMessages(exp); + + int endLine = reader.getLineNumber(); + File sourceFile = reader.getFile(); + ISourceLocation sl = + new SourceLocation(sourceFile, startLine, endLine, 0); + result.setSourceLocation(sl); + + if (clean) { + cleanup(result, reader); + } + return result; + } + + /** post-process result + * - use file name as keyword + * - clip / for dir offsets + * - extract purejava keyword variants + * - extract bugID + * - convert test options to force-options + * - detect illegal xml characters + */ + private void cleanup(AjcTest.Spec result, LineReader lineReader) { + LangUtil.throwIaxIfNull(result, "result"); + LangUtil.throwIaxIfNull(lineReader, "lineReader"); + + File suiteFile = lineReader.getFile(); + String name = suiteFile.getName(); + if (!name.endsWith(".txt")) { + throw new Error("unexpected name: " + name); + } + result.addKeyword("from-" + name.substring(0,name.length()-4)); + + final String dir = result.testDirOffset; + if (dir.endsWith("/")) { + result.testDirOffset = dir.substring(0,dir.length()-1); + } + + StringBuffer description = new StringBuffer(result.description); + if (strip(description, "PUREJAVA")) { + result.addKeyword("purejava"); + } + if (strip(description, "PUREJAVE")) { + result.addKeyword("purejava"); + } + if (strip(description, "[purejava]")) { + result.addKeyword("purejava"); + } + String input = description.toString(); + int loc = input.indexOf("PR#"); + if (-1 != loc) { + String prefix = input.substring(0, loc).trim(); + String pr = input.substring(loc+3, loc+6).trim(); + String suffix = input.substring(loc+6).trim(); + description.setLength(0); + description.append((prefix + " " + suffix).trim()); + try { + result.setBugId(Integer.valueOf(pr).intValue()); + } catch (NumberFormatException e) { + throw new Error("unable to convert " + pr + " for " + result + + " at " + lineReader); + } + } + input = description.toString(); + String error = null; + if (-1 != input.indexOf("&")) { + error = "char &"; + } else if (-1 != input.indexOf("<")) { + error = "char <"; + } else if (-1 != input.indexOf(">")) { + error = "char >"; + } else if (-1 != input.indexOf("\"")) { + error = "char \""; + } + if (null != error) { + throw new Error(error + " in " + input + " at " + lineReader); + } + result.description = input; + + ArrayList newOptions = new ArrayList(); + ArrayList optionsCopy = result.getOptionsList(); + for (Iterator iter = optionsCopy.iterator(); iter.hasNext();) { + String option = (String) iter.next(); + if (option.startsWith("-")) { + newOptions.add("!" + option.substring(1)); + } else { + throw new Error("non-flag option? " + option); + } + } + result.setOptionsArray((String[]) newOptions.toArray(new String[0])); + } + + private boolean strip(StringBuffer sb, String infix) { + String input = sb.toString(); + int loc = input.indexOf(infix); + if (-1 != loc) { + String prefix = input.substring(0, loc); + String suffix = input.substring(loc+infix.length()); + input = (prefix.trim() + " " + suffix.trim()).trim(); + sb.setLength(0); + sb.append(input); + return true; + } + return false; + } + + /** + * Generate list of expected messages of this kind. + * @param kind any non-null kind, but s.b. IMessage.WARNING or ERROR + * @param words + * @param start index in words where to start + * @param lastFile default file for source location if the input does not specify + * @return List + */ + private List makeMessages(// XXX weak - also support expected exceptions, etc. + Kind kind, String[] words, int start, File lastFile) { + ArrayList result = new ArrayList(); + for (int i = start; i < words.length; i++) { + ISourceLocation sl = + BridgeUtil.makeSourceLocation(words[i], lastFile); + if (null == sl) { // XXX signalling during make + // System.err.println(...); + //MessageUtil.debug(handler, "not a source location: " + words[i]); + } else { + String text = + (("" + sl.getLine()).equals(words[i]) ? "" : words[i]); + result.add(new Message(text, kind, null, sl)); + } + } + return (0 == result.size() ? Collections.EMPTY_LIST : result); + } + + /** + * Read suite spec from a flat .txt file. + * @throws AbortException on failure + * @return AjcTest.Suite.Spec with any AjcTest.Spec as children + */ + public AjcTest.Suite.Spec readSuite(File suiteFile) { + LangUtil.throwIaxIfNull(suiteFile, "suiteFile"); + if (!suiteFile.isAbsolute()) { + suiteFile = suiteFile.getAbsoluteFile(); + } + final AjcTest.Suite.Spec result = new AjcTest.Suite.Spec(); + result.setSuiteDirFile(suiteFile.getParentFile()); + ObjectChecker collector = new StandardObjectChecker(IRunSpec.class) { + public boolean doIsValid(Object o) { + result.addChild((IRunSpec) o); + return true; + } + }; + boolean abortOnError = true; + try { + READER.readNodes( + suiteFile, + collector, + abortOnError, + System.err); + } catch (IOException e) { + IMessage m = Message.fail("reading " + suiteFile, e); + throw new AbortException(m); + } + + return result; + } +} diff --git a/testing/src/org/aspectj/testing/harness/bridge/Globals.java b/testing/src/org/aspectj/testing/harness/bridge/Globals.java new file mode 100644 index 000000000..7c4f524a5 --- /dev/null +++ b/testing/src/org/aspectj/testing/harness/bridge/Globals.java @@ -0,0 +1,94 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + + + package org.aspectj.testing.harness.bridge; + +import org.aspectj.util.FileUtil; +import org.aspectj.util.LangUtil; + +import java.io.File; + +/** + */ +public class Globals { + /** name/key of the System property to set to define library dir */ + public static final String LIBDIR_NAME = "harness.libdir"; + + /** name/key of the System property to set to define J2SE_HOME dir */ + public static final String J2SE14_RTJAR_NAME = "j2se14.rtjar"; + + /** assumed relative location of a library with required jars */ + public static final String LIBDIR = getSystemProperty(LIBDIR_NAME, "../lib/test"); + + /** Path to J2SE_HOME */ + public static final File J2SE14_RTJAR; + + /** array of parameter types for main(String[]) */ + public static final Class[] MAIN_PARM_TYPES = new Class[] {String[].class}; + public static final String S_testingclient_jar = LIBDIR + "/testing-client.jar"; + public static final String S_aspectjrt_jar = LIBDIR + "/aspectjrt.jar"; + public static final String S_bridge_jar = LIBDIR + "/bridge.jar"; + public static final File F_testingclient_jar = new File(S_testingclient_jar); + public static final File F_aspectjrt_jar = new File(S_aspectjrt_jar); + public static final File F_bridge_jar = new File(S_bridge_jar); + public static final boolean globalsValid; + + static { + File j2seJar = null; + String path = getSystemProperty(J2SE14_RTJAR_NAME, "c:/home/apps/jdk14"); + File j2seHome = new File(path); + if (j2seHome.exists() && j2seHome.isDirectory()) { + File rtjar = new File(j2seHome.getAbsolutePath() + "/jre/lib/rt.jar"); + if (rtjar.canRead() && rtjar.isFile()) { + j2seJar = rtjar; + } + } + J2SE14_RTJAR = j2seJar; + globalsValid = + (FileUtil.canReadFile(F_testingclient_jar) + && FileUtil.canReadFile(F_aspectjrt_jar) + && FileUtil.canReadFile(F_bridge_jar) + && FileUtil.canReadFile(J2SE14_RTJAR) + ); + } + + /** + * + * @return null if not found, or + * String with class path for compiler to load J2SE 1.4 classes from. + */ + public static String get14Bootclasspath() { + return null; + } + + /** + * Get System property completely safely. + * @param propertyName the String name of the property to get + * @param defaultValue the String default value to return value is null or empty + * @return String value or defaultValue if not available. + */ + private static String getSystemProperty( + String propertyName, + String defaultValue) { + String result = defaultValue; + try { + String value = System.getProperty(propertyName); + if (!LangUtil.isEmpty(value)) { + result = value; + } + } catch (Throwable t) {} + return result; + } + +} diff --git a/testing/src/org/aspectj/testing/harness/bridge/IAjcRun.java b/testing/src/org/aspectj/testing/harness/bridge/IAjcRun.java new file mode 100644 index 000000000..061fde4d1 --- /dev/null +++ b/testing/src/org/aspectj/testing/harness/bridge/IAjcRun.java @@ -0,0 +1,44 @@ +/* ******************************************************************* + * Copyright (c) 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.harness.bridge; + +import org.aspectj.testing.run.IRun; +import org.aspectj.testing.run.IRunStatus; +import org.aspectj.testing.xml.XMLWriter; + +// XXX candidate to be subsumed in class/constructors, since inner spec does setup +// at the same time it constructs the run. +public interface IAjcRun extends IRun { + boolean setupAjcRun(Sandbox sandbox, Validator validator); + // XXX add for result eval? ArrayList getExpectedMessages(); + + /** this IAjcRun does nothing, returning true always */ + public static final IAjcRun NULLRUN = new IAjcRun() { + public boolean setupAjcRun(Sandbox sandbox, Validator validator) { + return true; + } + public boolean run(IRunStatus status) { + if (!status.started()) { + status.start(); + } + status.finish(IRunStatus.PASS); + return true; + } + + public void writeXml(XMLWriter out) { + throw new UnsupportedOperationException("unimplemented"); + } + public String toString() { return "IAjcRun.NULLRUN"; } + }; + +} diff --git a/testing/src/org/aspectj/testing/harness/bridge/IRunSpec.java b/testing/src/org/aspectj/testing/harness/bridge/IRunSpec.java new file mode 100644 index 000000000..7cb00765b --- /dev/null +++ b/testing/src/org/aspectj/testing/harness/bridge/IRunSpec.java @@ -0,0 +1,24 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.harness.bridge; + +import org.aspectj.testing.run.IRunIterator; +import org.aspectj.testing.xml.IXmlWritable; + +/** + * A run spec can make a run iterator and write itself as XML. + */ +public interface IRunSpec extends IXmlWritable { + IRunIterator makeRunIterator(Sandbox sandbox, Validator validator); +} diff --git a/testing/src/org/aspectj/testing/harness/bridge/IncCompilerRun.java b/testing/src/org/aspectj/testing/harness/bridge/IncCompilerRun.java new file mode 100644 index 000000000..efdd089fe --- /dev/null +++ b/testing/src/org/aspectj/testing/harness/bridge/IncCompilerRun.java @@ -0,0 +1,444 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.harness.bridge; + +import org.aspectj.bridge.ICommand; +import org.aspectj.bridge.IMessage; +import org.aspectj.bridge.MessageUtil; +import org.aspectj.testing.run.IRunIterator; +import org.aspectj.testing.run.IRunStatus; +import org.aspectj.testing.run.WrappedRunIterator; +import org.aspectj.testing.xml.SoftMessage; +import org.aspectj.testing.xml.XMLWriter; +import org.aspectj.util.FileUtil; +import org.aspectj.util.LangUtil; + +import java.io.File; +import java.io.FileFilter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * An incremental compiler run takes an existing compiler commmand + * from the sandbox, updates the staging directory, and recompiles. + * The staging directory is updated by prefix/suffix rules applied + * to files found below Sandbox.testBaseSrcDir. + * Files with suffix .{tag}.java are owned by this run + * and are copied to the staging directory + * unless they are prefixed "delete.", in which case the + * corresponding file is deleted. Any "owned" file is passed to + * the compiler as the list of changed files. + * The files entry contains the expected files recompiled. XXX underinclusive + * XXX prefer messages for expected files? + * XXX later: also support specified paths, etc. + */ +public class IncCompilerRun implements IAjcRun { + + final Spec spec; // nonfinal later to make re-runnable + Sandbox sandbox; + + /** + * @param handler must not be null, but may be reused in the same thread + */ + public IncCompilerRun(Spec spec) { + LangUtil.throwIaxIfNull(spec, "spec"); + this.spec = spec; + } + + /** + * Initialize this from the sandbox, using compiler and changedFiles. + * @param sandbox the Sandbox setup for this test, including copying + * any changed files, etc. + * @see org.aspectj.testing.harness.bridge.AjcTest.IAjcRun#setup(File, File) + * @throws AbortException containing IOException or IllegalArgumentException + * if the staging operations fail + */ + public boolean setupAjcRun(Sandbox sandbox, Validator validator) { + LangUtil.throwIaxIfNull(validator, "validator"); + if (!validator.nullcheck(sandbox, "sandbox") + || !validator.nullcheck(spec, "spec") + || !validator.nullcheck(spec.tag, "fileSuffix")) { + return false; + } + File srcDir = sandbox.getTestBaseSrcDir(this); + File destDir = sandbox.stagingDir; + if (!validator.canReadDir(srcDir, "testBaseSrcDir") + || !validator.canReadDir(destDir, "stagingDir")) { + return false; + } + + this.sandbox = sandbox; + return doStaging(validator); + } + + /** + * Handle copying and deleting of files per tag. + * This returns false unless some file was copied or deleted successfully + * and there were no failures copying or deleting files. + * @return true if staging completed successfully + */ + boolean doStaging(final Validator validator) { + boolean result = false; + try { + //ArrayList changed = new ArrayList(); + final String toSuffix = ".java"; + final String fromSuffix = "." + spec.tag + toSuffix; + // copy our tagged generation of files to the staging directory, + // deleting any with ChangedFilesCollector.DELETE_SUFFIX + class intHolder { + int numCopies; + int numDeletes; + int numFails; + } + final intHolder holder = new intHolder(); + FileFilter deleteOrCount = new FileFilter() { + final String clip = ".delete" + toSuffix; + /** do copy unless file should be deleted */ + public boolean accept(File file) { + boolean doCopy = true; + String path = file.getAbsolutePath(); + if (!path.endsWith(clip)) { + holder.numCopies++; + } else { + doCopy = false; + path = path.substring(0, path.length()-clip.length()) + toSuffix; + File toDelete = new File(path); + if (toDelete.delete()) { + holder.numDeletes++; + } else { + validator.fail("unable to delete file: " + path); + holder.numFails++; + } + } + return doCopy; + } + + }; + File srcDir = sandbox.getTestBaseSrcDir(this); + File destDir = sandbox.stagingDir; + FileUtil.copyDir(srcDir, destDir, fromSuffix, toSuffix, deleteOrCount); + if ((0 == holder.numCopies) && (0 == holder.numDeletes)) { + validator.fail("no files changed??"); + } else { + result = (0 == holder.numFails); + } + } catch (NullPointerException npe) { + validator.fail("staging - input", npe); + } catch (IOException e) { + validator.fail("staging - operations", e); + } + return result; + } + + /** + * @see org.aspectj.testing.run.IRun#run(IRunStatus) + */ + public boolean run(IRunStatus status) { + + ICommand compiler = sandbox.getCommand(this); + if (null == compiler) { + MessageUtil.abort(status, "null compiler"); + } + +// // This is a list of expected classes (in File-normal form +// // relative to base class/src dir, without .class suffix +// // -- like "org/aspectj/tools/ajc/Main") +// // A preliminary list is generated in doStaging. +// ArrayList expectedClasses = doStaging(status); +// if (null == expectedClasses) { +// return false; +// } +// +// // now add any (additional) expected-class entries listed in the spec +// // normalize to a similar file path (and do info messages for redundancies). +// +// List alsoChanged = spec.getPathsAsFile(sandbox.stagingDir); +// for (Iterator iter = alsoChanged.iterator(); iter.hasNext();) { +// File f = (File) iter.next(); +// +// if (expectedClasses.contains(f)) { +// // XXX remove old comment changed.contains() works b/c getPathsAsFile producing both File +// // normalizes the paths, and File.equals(..) compares these lexically +// String s = "specification of changed file redundant with tagged file: "; +// MessageUtil.info(status, s + f); +// } else { +// expectedClasses.add(f); +// } +// } +// +// // now can create handler, use it for reporting +// List errors = spec.getMessages(IMessage.ERROR); +// List warnings = spec.getMessages(IMessage.WARNING); +// AjcMessageHandler handler = new AjcMessageHandler(errors, warnings, expectedClasses); + + // same DirChanges handling for JavaRun, CompilerRun, IncCompilerRun + // XXX around advice or template method/class + DirChanges dirChanges = null; + if (!LangUtil.isEmpty(spec.dirChanges)) { + LangUtil.throwIaxIfFalse(1 == spec.dirChanges.size(), "expecting only 1 dirChanges"); + dirChanges = new DirChanges((DirChanges.Spec) spec.dirChanges.get(0)); + if (!dirChanges.start(status, sandbox.classesDir)) { + return false; // setup failed + } + } + List errors = spec.getMessages(IMessage.ERROR); + List warnings = spec.getMessages(IMessage.WARNING); + List expectRecompiled = Collections.EMPTY_LIST; + AjcMessageHandler handler = new AjcMessageHandler(errors, warnings, expectRecompiled); + boolean handlerResult = false; + boolean commandResult = false; + boolean result = false; + boolean report = false; + try { + handler.init(); + final long startTime = System.currentTimeMillis(); + commandResult = compiler.repeatCommand(handler); + // XXX disabled LangUtil.throwIaxIfNotAllAssignable(actualRecompiled, File.class, "recompiled"); + report = true; + // handler does not verify sandbox... + handlerResult = handler.passed(); + if (!handlerResult) { + result = false; + } else { + result = (commandResult == handler.expectingCommandTrue()); + if (! result) { + String m = commandResult + ? "incremental compile command did not return false as expected" + : "incremental compile command returned false unexpectedly"; + MessageUtil.fail(status, m); + } else if (null != dirChanges) { + result = dirChanges.end(status, sandbox.testBaseDir); + } + } + } finally { + if (!result || spec.runtime.isVerbose()) { // more debugging context in case of failure + MessageUtil.info(handler, "spec: " + spec.toLongString()); + MessageUtil.info(handler, "sandbox: " + sandbox); + String[] classes = FileUtil.listFiles(sandbox.classesDir); + MessageUtil.info(handler, "sandbox.classes: " + Arrays.asList(classes)); + } + // XXX weak - actual messages not reported in real-time, no fast-fail + if (report) { + handler.report(status); + } + } + return result; + } + + private boolean hasFile(ArrayList changed, File f) { + return changed.contains(f); // d + } + + + public String toString() { + return "" + spec; + // return "IncCompilerRun(" + spec + ")"; // XXX + } + +// /** @see IXmlWritable#writeXml(XMLWriter) */ +// public void writeXml(XMLWriter out) { +// String elementName = "inc-compile"; +// String tagAttr = out.makeAttribute("tag", spec.tag); +// List messages = spec.getMessages(); +// int nMessages = messages.size(); +// if (0 == nMessages) { +// out.printElement(elementName, tagAttr); +// } else { +// out.startElement(elementName, tagAttr, true); +// SoftMessage.writeXml(out, messages); +// out.endElement(elementName); +// } +// } +// + /** + * initializer/factory for IncCompilerRun. + */ + public static class Spec extends AbstractRunSpec { + public static final String XMLNAME = "inc-compile"; + + protected ArrayList classesAdded; + protected ArrayList classesRemoved; + protected ArrayList classesUpdated; + + /** + * skip description, skip sourceLocation, + * do keywords, skip options, do paths as classes, do comment, + * do dirChanges, do messages but skip children. + */ + private static final XMLNames NAMES = new XMLNames(XMLNames.DEFAULT, + "", "", null, "", "classes", null, false, false, true); + + /** identifies files this run owns, so {name}.{tag}.java maps to {name}.java */ + String tag; + + public Spec() { + super(XMLNAME); + setStaging(true); + classesAdded = new ArrayList(); + classesRemoved = new ArrayList(); + classesUpdated = new ArrayList(); + } + + public void setTag(String input) { + tag = input; + } + public String toString() { + return "IncCompile.Spec(" + tag + ", " + super.toString() + ")"; + } + + /** override to set dirToken to Sandbox.CLASSES and default suffix to ".class" */ + public void addDirChanges(DirChanges.Spec spec) { // XXX copy/paste of CompilerRun.Spec... + if (null == spec) { + return; + } + spec.setDirToken(Sandbox.CLASSES_DIR); + spec.setDefaultSuffix(".class"); + super.addDirChanges(spec); + } + + /** @return a IncCompilerRun with this as spec if setup completes successfully. */ + public IRunIterator makeRunIterator(Sandbox sandbox, Validator validator) { + IncCompilerRun run = new IncCompilerRun(this); + if (run.setupAjcRun(sandbox, validator)) { + // XXX need name + return new WrappedRunIterator(this, run); + } + return null; + } + + /** + * Write this out as a compile element as defined in + * AjcSpecXmlReader.DOCTYPE. + * @see AjcSpecXmlReader#DOCTYPE + * @see IXmlWritable#writeXml(XMLWriter) + */ + public void writeXml(XMLWriter out) { + String attr = out.makeAttribute("tag", tag); + out.startElement(xmlElementName, attr, false); + super.writeAttributes(out); + out.endAttributes(); + if (!LangUtil.isEmpty(dirChanges)) { + DirChanges.Spec.writeXml(out, dirChanges); + } + List messages = getMessages(); + if (0 < messages.size()) { + SoftMessage.writeXml(out, messages); + } + out.endElement(xmlElementName); + } + + public void setClassesAdded(String items) { + addItems(classesAdded, items); + } + + public void setClassesUpdated(String items) { + addItems(classesUpdated, items); + } + + public void setClassesRemoved(String items) { + addItems(classesRemoved, items); + } + + private void addItems(ArrayList list, String items) { + if (null != items) { + String[] classes = XMLWriter.unflattenList(items); + if (!LangUtil.isEmpty(classes)) { + for (int i = 0; i < classes.length; i++) { + if (!LangUtil.isEmpty(classes[i])) { + list.add(classes[i]); + } + } + } + } + } + } // class IncCompilerRun.Spec +} +// // XXX replaced with method-local class - revisit if useful +// +// /** +// * This class collects the list of all changed files and +// * deletes the corresponding file for those prefixed "delete." +// */ +// static class ChangedFilesCollector implements FileFilter { +// static final String DELETE_SUFFIX = ".delete.java"; +// static final String REPLACE_SUFFIX = ".java"; +// final ArrayList changed; +// final Validator validator; +// /** need this to generate paths by clipping */ +// final File destDir; +// +// /** @param changed the sink for all files changed (full paths) */ +// public ChangedFilesCollector(ArrayList changed, File destDir, Validator validator) { +// LangUtil.throwIaxIfNull(validator, "ChangedFilesCollector - handler"); +// this.changed = changed; +// this.validator = validator; +// this.destDir = destDir; +// } +// +// /** +// * This converts the input File to normal String path form +// * (without any source suffix) and adds it to the list changed. +// * If the name of the file is suffixed ".delete..", then +// * delete the corresponding file, and return false (no copy). +// * Return true otherwise (copy file). +// * @see java.io.FileFilter#accept(File) +// */ +// public boolean accept(File file) { +// final String aname = file.getAbsolutePath(); +// String name = file.getName(); +// boolean doCopy = true; +// boolean failed = false; +// if (name.endsWith(DELETE_SUFFIX)) { +// name = name.substring(0,name.length()-DELETE_SUFFIX.length()); +// file = file.getParentFile(); +// file = new File(file, name + REPLACE_SUFFIX); +// if (!file.canWrite()) { +// validator.fail("file to delete is not writable: " + file); +// failed = true; +// } else if (!file.delete()) { +// validator.fail("unable to delete file: " + file); +// failed = true; +// } +// doCopy = false; +// } +// if (!failed && doCopy) { +// int clip = FileUtil.sourceSuffixLength(file); +// if (-1 != clip) { +// name.substring(0, name.length()-clip); +// } +// if (null != destDir) { +// String path = destDir.getPath(); +// if (!LangUtil.isEmpty(path)) { +// // XXX incomplete +// if (name.startsWith(path)) { +// } else { +// int loc = name.lastIndexOf(path); +// if (-1 == loc) { // sigh +// +// } else { +// +// } +// } +// } +// } +// name = FileUtil.weakNormalize(name); +// changed.add(file); +// } +// return doCopy; +// } +// }; + diff --git a/testing/src/org/aspectj/testing/harness/bridge/JavaRun.java b/testing/src/org/aspectj/testing/harness/bridge/JavaRun.java new file mode 100644 index 000000000..5d7dd7010 --- /dev/null +++ b/testing/src/org/aspectj/testing/harness/bridge/JavaRun.java @@ -0,0 +1,298 @@ +/* ******************************************************************* + * Copyright (c) 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.harness.bridge; + +import org.aspectj.bridge.IMessageHandler; +import org.aspectj.bridge.MessageUtil; +import org.aspectj.testing.Tester; +import org.aspectj.testing.run.IRunIterator; +import org.aspectj.testing.run.IRunStatus; +import org.aspectj.testing.run.WrappedRunIterator; +import org.aspectj.testing.util.TestClassLoader; +import org.aspectj.testing.xml.SoftMessage; +import org.aspectj.testing.xml.XMLWriter; +import org.aspectj.util.FileUtil; +import org.aspectj.util.LangUtil; + +import java.io.File; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.URL; +import java.util.Arrays; +import java.util.List; + +/** + * Run a class in this VM using reflection. + */ +public class JavaRun implements IAjcRun { + Spec spec; + private Sandbox sandbox; + + /** programmatic initialization per spec */ + public JavaRun(Spec spec) { + this.spec = spec; + } + // XXX init(Spec) + + /** + * This checks the spec for a class name + * and checks the sandbox for a readable test source directory, + * a writable run dir, and (non-null, possibly-empty) lists + * of readable classpath dirs and jars. + * @return true if all checks pass + * @see org.aspectj.testing.harness.bridge.AjcTest.IAjcRun#setup(File, File) + */ + public boolean setupAjcRun(Sandbox sandbox, Validator validator) { + this.sandbox = sandbox; + return (validator.nullcheck(spec.className, "class name") + && validator.nullcheck(sandbox, "sandbox") + && validator.canReadDir(sandbox.getTestBaseSrcDir(this), "testBaseSrc dir") + && validator.canWriteDir(sandbox.runDir, "run dir") + && validator.canReadFiles(sandbox.getClasspathJars(true, this), "classpath jars") + && validator.canReadDirs(sandbox.getClasspathDirectories(true, this), "classpath dirs") + ); + + } + + /** caller must record any exceptions */ + public boolean run(IRunStatus status) + throws IllegalAccessException, + InvocationTargetException, + ClassNotFoundException, + NoSuchMethodException { + + boolean completedNormally = false; + Class targetClass = null; + if (!LangUtil.isEmpty(spec.dirChanges)) { + MessageUtil.info(status, "XXX dirChanges not implemented in JavaRun"); + } + try { + final boolean readable = true; + File[] libs = sandbox.getClasspathJars(readable, this); + URL[] urls = FileUtil.getFileURLs(libs); + File[] dirs = sandbox.getClasspathDirectories(readable, this); + ClassLoader loader = new TestClassLoader(urls, dirs); + // make the following load test optional + // Class testAspect = loader.loadClass("org.aspectj.lang.JoinPoint"); + targetClass = loader.loadClass(spec.className); + Method main = targetClass.getMethod("main", Globals.MAIN_PARM_TYPES); + setupTester(sandbox.getTestBaseSrcDir(this), loader, status); + main.invoke(null, new Object[] { spec.getOptionsArray() }); + completedNormally = true; + } catch (ClassNotFoundException e) { + String[] classes = FileUtil.listFiles(sandbox.classesDir); + MessageUtil.info(status, "sandbox.classes: " + Arrays.asList(classes)); + MessageUtil.fail(status, null, e); + } finally { + if (!completedNormally) { + MessageUtil.info(status, spec.toLongString()); + MessageUtil.info(status, "targetClass: " + targetClass); + MessageUtil.info(status, "" + sandbox); + } + } + return completedNormally; + } + + /** + * Clear (static) testing state and setup base directory, + * unless spec.skipTesting. + * @return null if successful, error message otherwise + */ + protected void setupTester(File baseDir, ClassLoader loader, IMessageHandler handler) { + if (null == loader) { + setupTester(baseDir, handler); + return; + } + File baseDirSet = null; + try { + if (!spec.skipTester) { + Class tc = loader.loadClass("org.aspectj.testing.Tester"); + // Tester.clear(); + Method m = tc.getMethod("clear", new Class[0]); + m.invoke(null, new Object[0]); + // Tester.setMessageHandler(handler); + m = tc.getMethod("setMessageHandler", new Class[] {IMessageHandler.class}); + m.invoke(null, new Object[] { handler}); + + //Tester.setBASEDIR(baseDir); + m = tc.getMethod("setBASEDIR", new Class[] {File.class}); + m.invoke(null, new Object[] { baseDir}); + + //baseDirSet = Tester.getBASEDIR(); + m = tc.getMethod("getBASEDIR", new Class[0]); + baseDirSet = (File) m.invoke(null, new Object[0]); + + if (!baseDirSet.equals(baseDir)) { + String l = "AjcScript.setupTester() setting " + + baseDir + " returned " + baseDirSet; + MessageUtil.debug(handler, l); + } + } + } catch (Throwable t) { + MessageUtil.abort(handler, "baseDir=" + baseDir, t); + } + } + + /** + * Clear (static) testing state and setup base directory, + * unless spec.skipTesting. + * This implementation assumes that Tester is defined for the + * same class loader as this class. + * @return null if successful, error message otherwise + */ + protected void setupTester(File baseDir, IMessageHandler handler) { + File baseDirSet = null; + try { + if (!spec.skipTester) { + Tester.clear(); + Tester.setMessageHandler(handler); + Tester.setBASEDIR(baseDir); + baseDirSet = Tester.getBASEDIR(); + if (!baseDirSet.equals(baseDir)) { + String l = "AjcScript.setupTester() setting " + + baseDir + " returned " + baseDirSet; + MessageUtil.debug(handler, l); + } + } + } catch (Throwable t) { + MessageUtil.abort(handler, "baseDir=" + baseDir, t); + } + } + public String toString() { + return "JavaRun(" + spec + ")"; + } + + /** + * Initializer/factory for JavaRun. + * The classpath is not here but precalculated in the Sandbox. XXX libs? + */ + public static class Spec extends AbstractRunSpec { + public static final String XMLNAME = "run"; + /** + * skip description, skip sourceLocation, + * do keywords, do options, skip paths, do comment, + * do dirChanges, do messages but skip children. + */ + private static final XMLNames NAMES = new XMLNames(XMLNames.DEFAULT, + "", "", null, null, "", null, false, false, true); + + /** fully-qualified name of the class to run */ + protected String className; + + /** minimum required version of Java, if any */ + protected String javaVersion; + + /** if true, skip Tester setup (e.g., if Tester n/a) */ + protected boolean skipTester; + + public Spec() { + super(XMLNAME); + setXMLNames(NAMES); + } + + /** + * @param version "1.1", "1.2", "1.3", "1.4" + * @throws IllegalArgumentException if version is not recognized + */ + public void setJavaVersion(String version) { + LangUtil.supportsJava(version); + this.javaVersion = version; + } + + /** @className fully-qualified name of the class to run */ + public void setClassName(String className) { + this.className = className; + } + + /** @param skip if true, then do not set up Tester */ + public void setSkipTester(boolean skip) { + skipTester = skip; + } + + /** override to set dirToken to Sandbox.RUN_DIR */ + public void addDirChanges(DirChanges.Spec spec) { + if (null == spec) { + return; + } + spec.setDirToken(Sandbox.RUN_DIR); + super.addDirChanges(spec); + } + + /** @return a JavaRun with this as spec if setup completes successfully. */ + public IRunIterator makeRunIterator(Sandbox sandbox, Validator validator) { + JavaRun run = new JavaRun(this); + if (run.setupAjcRun(sandbox, validator)) { + // XXX need name for JavaRun + return new WrappedRunIterator(this, run); + } + return null; + } + + /** + * Write this out as a run element as defined in + * AjcSpecXmlReader.DOCTYPE. + * @see AjcSpecXmlReader#DOCTYPE + * @see IXmlWritable#writeXml(XMLWriter) + */ + public void writeXml(XMLWriter out) { + String attr = out.makeAttribute("class", className); + out.startElement(xmlElementName, attr, false); + if (skipTester) { + out.printAttribute("skipTester", "true"); + } + if (null != javaVersion) { + out.printAttribute("vm", javaVersion); + } + super.writeAttributes(out); + out.endAttributes(); + if (!LangUtil.isEmpty(dirChanges)) { + DirChanges.Spec.writeXml(out, dirChanges); + } + List messages = getMessages(); + if (0 < messages.size()) { + SoftMessage.writeXml(out, messages); + } + out.endElement(xmlElementName); + } + public String toLongString() { + return toString() + "[" + super.toLongString() + "]"; + } + + public String toString() { + if (skipTester) { + return "JavaRun(" + className + ", skipTester)"; + } else { + return "JavaRun(" + className + ")"; + } + } + + /** + * This implementation skips if: + *
      + *
    • current VM is not at least any specified javaVersion
    • + *
    + * @return false if this wants to be skipped, true otherwise + */ + protected boolean doAdoptParentValues(RT parentRuntime, IMessageHandler handler) { + if (!super.doAdoptParentValues(parentRuntime, handler)) { + return false; + } + if ((null != javaVersion) && (!LangUtil.supportsJava(javaVersion))) { + skipMessage(handler, "requires Java version " + javaVersion); + return false; + } + return true; + } + } +} + diff --git a/testing/src/org/aspectj/testing/harness/bridge/RunSpecIterator.java b/testing/src/org/aspectj/testing/harness/bridge/RunSpecIterator.java new file mode 100644 index 000000000..ebc4c14c6 --- /dev/null +++ b/testing/src/org/aspectj/testing/harness/bridge/RunSpecIterator.java @@ -0,0 +1,256 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.harness.bridge; + +import org.aspectj.bridge.IMessage; +import org.aspectj.bridge.IMessageHandler; +import org.aspectj.bridge.Message; +import org.aspectj.testing.run.IRun; +import org.aspectj.testing.run.IRunIterator; +import org.aspectj.testing.run.Runner; +import org.aspectj.testing.run.WrappedRunIterator; +import org.aspectj.util.LangUtil; + +import java.util.ArrayList; + + +/** + * This wraps an AbstractRunSpec, which has children that + * return IRunIterator, the results of which we return + * from nextRun(..) + * We extract global options from the AbstractRunSpec options + * and set the global options in the AbstractRunSpec, + * which is responsible for setting them in any children + * during makeRun(..). + */ +public class RunSpecIterator implements IRunIterator { + /* + * When constructed, this gets its own spec + * and a sandbox to be used for making all children. + * In nextRun() this uses the spec's child specs + * and the sandbox to create the next child run iterator. + * This returns all the run provided by that child iterator + * before going to the next child. + */ + + /** spec for this test */ + public final AbstractRunSpec spec; // XXX reconsider public after debugging + + /** current sandbox by default shared by all children */ + Sandbox sandbox; + + /** keep our copy to avoid recopying */ + ArrayList childSpecs; + + /** index into child specs of next run */ + int specIndex; + + /** child creation until the start of each run */ + final Validator validator; + + /** current child iterator */ + IRunIterator childIterator; + + final boolean haltOnFailure; + + private int numIncomplete; + + private final IMessage.Kind failureKind; + + private boolean didCleanup; + + /** + * Create a RunSpecIterator. + * Failure messages are of type IMessage.ABORT if abortOnFailure is true, + * or IMessage.ERROR otherwise. + * @param spec the AbstractRunSpec whose children we iterate - not null + * @param sandbox the default Sandbox to use for children to make runs - may be null + * @param haltOnFailure if true, stop after any failure in providing runs + */ + public RunSpecIterator( + AbstractRunSpec spec, + Sandbox sandbox, + Validator validator, + boolean haltOnFailure) { + this(spec, sandbox, validator, haltOnFailure, + (haltOnFailure ? IMessage.ABORT : IMessage.ERROR)); + } + + /** + * Create a RunSpecIterator, specifying any failure message kind. + * @param spec the AbstractRunSpec whose children we iterate - not null + * @param sandbox the default Sandbox to use for children to make runs - may be null + * @param haltOnFailure if true, stop after any failure in providing runs + * @param failureKind the IMessage.Kind for any failure messages - if null, no messages sent + */ + public RunSpecIterator( + AbstractRunSpec spec, + Sandbox sandbox, + Validator validator, + boolean haltOnFailure, + IMessage.Kind failureKind) { + LangUtil.throwIaxIfNull(spec, "spec"); + LangUtil.throwIaxIfNull(sandbox, "sandbox"); + LangUtil.throwIaxIfNull(validator, "validator"); + this.sandbox = sandbox; + this.spec = spec; + this.validator = validator; + this.haltOnFailure = haltOnFailure; + this.failureKind = failureKind; + reset(); + } + + /** + * @return value set on construction for abortOnError + * @see org.aspectj.testing.run.IRunIterator#abortOnFailure() + */ + public boolean abortOnFailure() { + return haltOnFailure; + } + + /** reset to start at the beginning of the child specs. */ + public void reset() { + specIndex = 0; + childSpecs = spec.getWorkingChildren(); + childIterator = null; + numIncomplete = 0; + } + + /** @return int number of child run attempts that did not produce IRun */ + public int getNumIncomplete() { + return numIncomplete; + } + + /** + * @see org.aspectj.testing.run.IRunIterator#hasNextRun() + */ + public boolean hasNextRun() { + return ((specIndex < childSpecs.size()) + || ((null != childIterator) + && childIterator.hasNextRun())); + } + + /** + * Get the next child IRunIterator as an IRun. + * In case of failure to get the next child, + * numIncomplete is incremented, and + * a message of type failureKind is passed to the handler + * (if failureKind was defined in the contructor). + * @return next child IRunIterator wrapped as an IRun + */ + public IRun nextRun(final IMessageHandler handler, Runner runner) { + validator.pushHandler(handler); + try { + IRun result = null; + IRunSpec childSpec = null; + String error = null; + String specLabel = "getting run for child of \"" + spec + "\" "; + while ((null == result) && hasNextRun() && (null == error)) { + if (null == childIterator) { + childSpec = (IRunSpec) childSpecs.get(specIndex++); + if (null == childSpec) { + error = "unexpected - no more child specs at " + --specIndex; + } else { + Sandbox sandbox = makeSandbox(childSpec, validator); + if (null == sandbox) { + error = "unable to make sandbox for \"" + childSpec + "\""; + childIterator = null; + } else { + IRunIterator iter = childSpec.makeRunIterator(sandbox, validator); + if (null == iter) { + // client should read reason why from validator + error = "child \"" + childSpec + "\".makeRunIterator(..) returned null"; + } else { + // hoist: result not wrapped but single IRun + if ((iter instanceof WrappedRunIterator)) { + if (!iter.hasNextRun()) { + error = "child \"" + childSpec + "\".hasNextRun()" + + " is not true - should be exactly one run"; + } else { + result = iter.nextRun(handler, runner); + if (null == result) { + error = "child \"" + childSpec + "\".nextRun()" + + " returned null - should be exactly one run"; + } else { + childIterator = null; + return result; + } + } + } else { + childIterator = iter; + } + } + } + } + } + if (null != childIterator) { + result = runner.wrap(childIterator, null); + childIterator = null; + } else if (null != error) { + numIncomplete++; + if (null != failureKind) { + handler.handleMessage(new Message(specLabel + error, failureKind, null, null)); + } + if (!haltOnFailure) { + error = null; + } else if (result != null) { + result = null; // do not return result if halting due to failure + } + } + } + return result; + } finally { + validator.popHandler(handler); + } + } + + /** + * @see org.aspectj.testing.run.IRunIterator#iterationCompleted() + */ + public void iterationCompleted() { + } + + public String toString() { + return "" + spec; + //return "RunSpecIterator(" + specIndex + ", " + spec + ")" ; + } + + /* + * Subclasses may: + * - set the sandbox on construction + * - lazily set it on first use + * - set it for each child + */ + + /** + * Create the sandbox used for each child. + * This implementation always uses the sandbox set on construction. + * Subclasses may decide to create one sandbox per child iterator. + */ + protected Sandbox makeSandbox(IRunSpec child, Validator validator) { + return getSandbox(); + } + + /** Get the sandbox currently in use */ + protected Sandbox getSandbox() { + return sandbox; + } + + /** Set the sandbox currently in use */ + protected void setSandbox(Sandbox sandbox) { + LangUtil.throwIaxIfNull(sandbox, "sandbox"); + this.sandbox = sandbox; + } + +} diff --git a/testing/src/org/aspectj/testing/harness/bridge/Sandbox.java b/testing/src/org/aspectj/testing/harness/bridge/Sandbox.java new file mode 100644 index 000000000..6f4984bac --- /dev/null +++ b/testing/src/org/aspectj/testing/harness/bridge/Sandbox.java @@ -0,0 +1,386 @@ +/* ******************************************************************* + * Copyright (c) 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.harness.bridge; + +import org.aspectj.bridge.ICommand; +import org.aspectj.bridge.IMessage; +import org.aspectj.bridge.IMessageHandler; +import org.aspectj.testing.util.Diffs; +import org.aspectj.util.FileUtil; +import org.aspectj.util.LangUtil; + +import java.io.File; +import java.util.ArrayList; + +/** + * A sandbox holds state shared by AjcTest sub-runs, + * mostly directories relevant to testing. + * It permits a limited amount of coordination and + * setup/cleanup operations (todo XXX). + *

    + * AjcTest creates the Sandbox and initializes the final fields. + * To coordinate with each other, run components may set and get values, + * with the sources running first and the sinks second. + * To make the interactions clear + * (and to avoid accidentally violating these semantics), + * setters/getters for a coordinated property are constrained two ways: + *

  • Both have an extra (typed) "caller" parameter which must not + * be null, authenticating that the caller is known & valid.
  • + *
  • A getter throws IllegalStateException if called before the setter
  • + *
  • A setter throws IllegalStateException if called after the getter
  • + * XXX subclass more general sandbox? + */ +public class Sandbox { + /** classes directory token for DirChanges.Spec */ + public static final String RUN_DIR = "run"; + + /** run directory token for DirChanges.Spec */ + public static final String CLASSES_DIR = "classes"; + + private static boolean canRead(File dir) { + return ((null != dir) && dir.isDirectory() && dir.canRead()); + } + + private static boolean canWrite(File dir) { + return ((null != dir) && dir.isDirectory() && dir.canWrite()); + } + + private static void iaxWrite(File dir, String label) { + if (!canWrite(dir)) { + throw new IllegalArgumentException(label + " - " + dir); + } + } + + private static void iaxRead(File dir, String label) { + if (!canRead(dir)) { + throw new IllegalArgumentException(label + " - " + dir); + } + } + + /** @throws IllegalStateException(message) if test */ + private static void assertState(boolean test, String message) { + if (!test) { + throw new IllegalStateException(message); + } + } + + /** + * The (read-only) base of the test sources (which may or may not + * be the base of the java sources) + */ + public final File testBaseDir; + + /** the parent of a temporary workspace, probably includes some others */ + public final File sandboxDir; + + /** a shared working dir */ + public final File workingDir; + + /** a shared classes dir */ + public final File classesDir; + + /** a run dir (which will be ignored in non-forking runs) */ + public final File runDir; + + /** staging directory for IAjcRun requiring files be copied, deleted, etc. */ + public final File stagingDir; + + /** + * This manages creation and deletion of temporary directories. + * We hold a reference so that our clients can signal whether + * this should be deleted. + */ + private final Validator validator; // XXX required after completing tests? + + /** original base of the original java sources, set by CompileRun.setup(..) */ + private File testBaseSrcDir; + + /** directories and libraries on the classpath, set by CompileRun.setup(..) */ + private File[] classpath; + + /** track whether classpath getter ran */ + private boolean gotClasspath; + + /** command shared between runs using sandbox - i.e., compiler */ + private ICommand command; + + /** track whether command getter ran */ + private boolean gotCommand; + + /** cache results of rendering final fields */ + private transient String toStringLeader; + + /** @throws IllegalArgumentException unless validator validates + * testBaseDir as readable + */ + public Sandbox(File testBaseDir, Validator validator) { + LangUtil.throwIaxIfNull(validator, "validator"); + this.validator = validator; + Sandbox.iaxRead(testBaseDir, "testBaseDir"); + this.testBaseDir = testBaseDir; + + sandboxDir = FileUtil.getTempDir("Sandbox"); + Sandbox.iaxWrite(sandboxDir, "sandboxDir"); // XXX not really iax + + workingDir = FileUtil.makeNewChildDir(sandboxDir, "workingDir"); + Sandbox.iaxWrite(workingDir, "workingDir"); + + classesDir = FileUtil.makeNewChildDir(sandboxDir, "classes"); + Sandbox.iaxWrite(classesDir, "classesDir"); + + runDir = FileUtil.makeNewChildDir(sandboxDir, "run"); + Sandbox.iaxWrite(runDir, "runDir"); + + stagingDir = FileUtil.makeNewChildDir(sandboxDir, "staging"); + Sandbox.iaxWrite(stagingDir, "stagingDir"); + + validator.registerSandbox(this); + } + + private String getToStringLeader() { + if (null == toStringLeader) { + toStringLeader = "Sandbox(" + sandboxDir.getName() + + ", " + testBaseSrcDir.getName(); + } + return toStringLeader; + } + + /** @return "Sandbox(sandbox, src, classes)" with names only */ + public String toString() { + return getToStringLeader() + ", " + classesDir.getName() + ")"; + } + + /** @return "Sandbox(sandbox, src, classes)" with paths */ + public String toLongString() { + return getToStringLeader() + ", " + classesDir.getPath() + + (null == command ? ", (null command)" : ", " + command) + ")"; + } + + void setCommand(ICommand command, CompilerRun caller) { + LangUtil.throwIaxIfNull(caller, "caller"); + LangUtil.throwIaxIfNull(command, "command"); + LangUtil.throwIaxIfFalse(!gotCommand, "no command"); + this.command = command; + } + + /** When test is completed, clear the compiler to avoid memory leaks */ + void clearCommand(AjcTest caller) { + LangUtil.throwIaxIfNull(caller, "caller"); + if (null != command) { + command = null; + } + } + +// /** +// * Populate the staging directory by copying any files in the +// * source directory ending with fromSuffix +// * to the staging directory, after renaming them with toSuffix. +// * If the source file name starts with "delete", then the +// * corresponding file in the staging directory is deleting. +// * @return a String[] of the files copied or deleted +// * (path after suffix changes and relative to staging dir) +// * @throws Error if no File using fromSuffix are found +// */ +// String[] populateStagingDir(String fromSuffix, String toSuffix, IAjcRun caller) { +// LangUtil.throwIaxIfNull(fromSuffix, "fromSuffix"); +// LangUtil.throwIaxIfNull(toSuffix, "toSuffix"); +// LangUtil.throwIaxIfNull(caller, "caller"); +// +// ArrayList result = new ArrayList(); +// FileUtil.copyDir( +// srcBase, +// targetSrc, +// fromSuffix, +// toSuffix, +// collector); +// +// final String canonicalFrom = srcBase.getCanonicalPath(); +// final Definition[] defs = getDefinitions(srcBase); +// if ((null == defs) || (defs.length < 9)) { +// throw new Error("did not get definitions"); +// } +// MessageHandler compilerMessages = new MessageHandler(); +// StringBuffer commandLine = new StringBuffer(); +// for (int i = 1; result && (i < 10); i++) { +// String fromSuffix = "." + i + "0.java"; +// // copy files, collecting as we go... +// files.clear(); +// if (0 == files.size()) { // XXX detect incomplete? +// break; +// } +// +// +// return (String[]) result.toArray(new String[0]); +// } + + // XXX move to more general in FileUtil + void reportClassDiffs( + final IMessageHandler handler, + IncCompilerRun caller, + long classesDirStartTime, + String[] expectedSources) { + LangUtil.throwIaxIfFalse(0 < classesDirStartTime, "0 >= " + classesDirStartTime); + boolean acceptPrefixes = true; + Diffs diffs = org.aspectj.testing.util.FileUtil.dirDiffs( + "classes", + classesDir, + classesDirStartTime, + ".class", + expectedSources, + acceptPrefixes); + diffs.report(handler, IMessage.ERROR); + } + +// // XXX replace with IMessage-based implementation +// // XXX move to more general in FileUtil +// void reportClassesDirDiffs(final IMessageHandler handler, IncCompilerRun caller, +// String[] expectedSources) { +// // normalize sources to ignore +// final ArrayList sources = new ArrayList(); +// if (!LangUtil.isEmpty(expectedSources)) { +// for (int i = 0; i < expectedSources.length; i++) { +// String srcPath = expectedSources[i]; +// int clip = FileUtil.sourceSuffixLength(srcPath); +// if (0 != clip) { +// srcPath = srcPath.substring(0, srcPath.length() - clip); +// sources.add(FileUtil.weakNormalize(srcPath)); +// } else if (srcPath.endsWith(".class")) { +// srcPath = srcPath.substring(0, srcPath.length() - 6); +// sources.add(FileUtil.weakNormalize(srcPath)); +// } else { +// MessageUtil.info(handler, "not source file: " + srcPath); +// } +// } +// } +// +// // gather, normalize paths changed +// final ArrayList changed = new ArrayList(); +// FileFilter touchedCollector = new FileFilter() { +// public boolean accept(File file) { +// if (file.lastModified() > classesDirTime) { +// String path = file.getPath(); +// if (!path.endsWith(".class")) { +// MessageUtil.info(handler, "changed file not a class: " + file); +// } else { +// String classPath = path.substring(0, path.length() - 6); +// classPath = FileUtil.weakNormalize(classPath); +// if (sources.contains(classPath)) { +// sources.remove(classPath); +// } else { +// changed.add(classPath); +// } +// } +// } +// return false; +// } +// }; +// classesDir.listFiles(touchedCollector); +// +// // report any unexpected changes +// Diffs diffs = new Diffs("classes", sources, changed, String.CASE_INSENSITIVE_ORDER); +// diffs.report(handler, IMessage.ERROR); +// } + + ICommand getCommand(IncCompilerRun caller) { + LangUtil.throwIaxIfNull(caller, "caller"); + assertState(null != command, "command never set"); + return command; + } + + File getTestBaseSrcDir(IncCompilerRun caller) { + LangUtil.throwIaxIfNull(caller, "caller"); + return testBaseSrcDir; + } + + File getTestBaseSrcDir(JavaRun caller) { + LangUtil.throwIaxIfNull(caller, "caller"); + return testBaseSrcDir; + } + + /** @throws IllegalArgumentException unless a readable directory */ + void setTestBaseSrcDir(File dir, CompilerRun caller) { + LangUtil.throwIaxIfNull(caller, "caller"); + if ((null == dir) || !dir.isDirectory() || !dir.canRead()) { + throw new IllegalArgumentException("bad test base src dir: " + dir); + } + testBaseSrcDir = dir; + } + + /** @param readable if true, then throw IllegalArgumentException if not readable */ + void setClasspath(File[] files, boolean readable, CompilerRun caller) { + LangUtil.throwIaxIfNull(files, "files"); + LangUtil.throwIaxIfNull(caller, "caller"); + assertState(!gotClasspath, "classpath already read"); + classpath = new File[files.length]; + for (int i = 0; i < files.length; i++) { + LangUtil.throwIaxIfNull(files[i], "files[i]"); + if (readable && !files[i].canRead()) { + throw new IllegalArgumentException("bad classpath entry: " + files[i]); + } + classpath[i] = files[i]; + } + } + + File[] getClasspath(JavaRun caller) { + LangUtil.throwIaxIfNull(caller, "caller"); + assertState(null != classpath, "classpath not set"); + + File[] result = new File[classpath.length]; + System.arraycopy(classpath, 0, result, 0, result.length); + return result; + } + + /** @param readable if true, omit non-readable directories */ + File[] getClasspathDirectories(boolean readable, JavaRun caller) { + LangUtil.throwIaxIfNull(caller, "caller"); + assertState(null != classpath, "classpath not set"); + ArrayList result = new ArrayList(); + File[] src = classpath; + for (int i = 0; i < src.length; i++) { + File f = src[i]; + if ((null != f) && (f.isDirectory()) && (!readable || f.canRead())) { + result.add(f); + } + } + return (File[]) result.toArray(new File[0]); + } + + /** @param readable if true, omit non-readable directories */ + File[] getClasspathJars(boolean readable, JavaRun caller) { + LangUtil.throwIaxIfNull(caller, "caller"); + assertState(null != classpath, "classpath not set"); + ArrayList result = new ArrayList(); + File[] src = classpath; + for (int i = 0; i < src.length; i++) { + File f = src[i]; + if (FileUtil.hasZipSuffix(f) && (!readable || f.canRead())) { + result.add(f); + } + } + return (File[]) result.toArray(new File[0]); + } + + /** @return String of classpath entries delimited internally by File.pathSeparator */ + String classpathToString(CompilerRun caller) { + LangUtil.throwIaxIfNull(caller, "caller"); + assertState(null != classpath, "classpath not set"); + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < classpath.length; i++) { + if (i > 0) { + sb.append(File.pathSeparator); + } + sb.append(classpath[i].getAbsolutePath()); + } + return sb.toString(); + } +} diff --git a/testing/src/org/aspectj/testing/harness/bridge/Validator.java b/testing/src/org/aspectj/testing/harness/bridge/Validator.java new file mode 100644 index 000000000..55c3962e8 --- /dev/null +++ b/testing/src/org/aspectj/testing/harness/bridge/Validator.java @@ -0,0 +1,557 @@ +/* ******************************************************************* + * Copyright (c) 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.harness.bridge; + +import org.aspectj.bridge.AbortException; +import org.aspectj.bridge.IMessage; +import org.aspectj.bridge.IMessageHandler; +import org.aspectj.bridge.MessageUtil; +import org.aspectj.util.FileUtil; +import org.aspectj.util.LangUtil; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.ListIterator; +import java.util.Stack; + +/** + * Check input and implement defaults. + * This handles failure messaging and collecting temp directories + * for later cleanup. + * The default behavior is to send a fail message to the message + * handler. Clients may instead: + *
  • Toggle abortOnException to throw AbortException on failure
  • + *
  • push their own message handler to redirect messages + * (pop to remove if any pushed)
  • + *
  • subclass this to reimplement fail(String) + *

    + * A component given this to do validation should + * not change the reporting scheme established by the caller, + * so the caller may lock and unlock the error handling policy. + * When the policy is locked, this silently ignores attempts + * to toggle exceptions, or delete temporary files. + * XXX callers cannot prevent others from pushing other error handlers. + */ +public class Validator { + + /** stack of handlers */ + private final Stack handlers; + + /** list of File registered for deletion on demand */ + private final ArrayList tempFiles; // deleteTempFiles requires ListIterator.remove() + + /** list of Sandboxes registered for cleanup on demand */ + private final ArrayList sandboxes; + + /** if true, throw AbortException on failure */ + boolean abortOnFailure; + + /** this object prevents any changes to error-handling policy */ + private Object locker; + + public Validator(IMessageHandler handler) { + tempFiles = new ArrayList(); + sandboxes = new ArrayList(); + handlers = new Stack(); + pushHandler(handler); + } + + /** + * Push IMessageHandler onto stack, + * so it will be used until the next push or pop + * @param handler not null + * + */ + public void pushHandler(IMessageHandler handler) { + LangUtil.throwIaxIfNull(handler, "handler"); + handlers.push(handler); + } + + /** @throws IllegalStateException if handler is not on top */ + public void popHandler(IMessageHandler handler) { + LangUtil.throwIaxIfNull(handler, "handler"); + if (handler != handlers.peek()) { + throw new IllegalStateException("not current handler"); + } + handlers.pop(); + } + + /** @return true if this requestor now has locked the error handling policy */ + public boolean lock(Object requestor) { + if (null == locker) { + locker = requestor; + } + return (locker == requestor); + } + + /** @return true if the error handling policy is now unlocked */ + public boolean unlock(Object requestor) { + if (requestor == locker) { + locker = null; + } + return (locker == null); + } + + public void setAbortOnFailure(boolean abortOnFailure) { + if (null == locker) { + if (this.abortOnFailure != abortOnFailure) { + this.abortOnFailure = abortOnFailure; + } + } + } + + /** + * May fail with any of the messages + *

  • {null check} array
  • + *
  • {null check} {message}[#}
  • + */ + public boolean nullcheck(Object[] ra, String message) { + return ((nullcheck((Object) ra, message + " array")) + && nullcheck(Arrays.asList(ra), message)); + } + + /** + * Like nullcheck(Collection, message), except adding lower and upper bound + * @param atLeast fail if list size is smaller than this + * @param atMost fail if list size is greater than this + */ + public boolean nullcheck(Collection list, int atLeast, int atMost, String message) { + if (nullcheck(list, message)) { + int size = list.size(); + if (size < atLeast) { + fail(message + ": " + size + "<" + atLeast); + } else if (size > atMost) { + fail(message + ": " + size + ">" + atMost); + } else { + return true; + } + } + return false; + } + + /** + * May fail with any of the messages + *
  • {null check} list
  • + *
  • {null check} {message}[#}
  • + */ + public boolean nullcheck(Collection list, String message) { + if (nullcheck((Object) list, message + " list")) { + int i = 0; + for (Iterator iter = list.iterator(); iter.hasNext();) { + if (!nullcheck(iter.next(), message + "[" + i++ + "]")) { + return false; + } + } + return true; + } + return false; + } + + /** + * May fail with the message "null {message}" + * if o and the def{ault} are null + * @return o if not null or default otherwise + */ + public Object nulldefault(Object o, String message, Object def) { + if (null == o) { + o = def; + } + nullcheck(o, message); + return o; + } + + /** may fail with the message "null {message}" */ + public boolean nullcheck(Object o, String message) { + if (null == o) { + if (null == message) message = "object"; + fail("null " + message); + return false; + } + return true; + } + + /** + * Verify that all paths are readable relative to baseDir. + * may fail with the message "cannot read {file}" + */ + public boolean canRead(File baseDir, String[] paths, String message) { + if (!canRead(baseDir, "baseDir - " + message) + || !nullcheck(paths, "paths - " + message)) { + return false; + } + final String dirPath = baseDir.getPath(); + File[] files = FileUtil.getBaseDirFiles(baseDir, paths); + for (int j = 0; j < files.length; j++) { + if (!canRead(files[j], "{" + dirPath + "} " + files[j])) { + return false; + } + } + return true; + } + + /** + * Verify that all paths are readable relative to baseDir. + * may fail with the message "cannot read {file}" + */ + public boolean canRead(File[] files, String message) { + if (!nullcheck(files, message)) { + return false; + } + for (int j = 0; j < files.length; j++) { + if (!canRead(files[j], files[j].getPath())) { + return false; + } + } + return true; + } + + /** may fail with the message "cannot read {file}" */ + public boolean canRead(File file, String message) { + if (nullcheck(file, message)) { + if (file.canRead()) { + return true; + } else { + fail("cannot read " + file); + } + } + return false; + } + + /** may fail with the message "cannot write {file}" */ + public boolean canWrite(File file, String message) { + if (nullcheck(file, message)) { + if (file.canRead()) { + return true; + } else { + fail("cannot write " + file); + } + } + return false; + } + + /** may fail with the message "not a directory {file}" */ + public boolean canReadDir(File file, String message) { + if (canRead(file, message)) { + if (file.isDirectory()) { + return true; + } else { + fail("not a directory " + file); + } + } + return false; + } + + /** may fail with the message "not a directory {file}" */ + public boolean canWriteDir(File file, String message) { + if (canWrite(file, message)) { + if (file.isDirectory()) { + return true; + } else { + fail("not a directory " + file); + } + } + return false; + } + + /** + * May fail with any of the messages + *
  • {null check} dir array
  • + *
  • "#: not a File {file}"
  • + *
  • {canRead} {message}[#}
  • + */ + public boolean canReadFiles(Object[] dirs, String message) { + return ((nullcheck((Object) dirs, message + " dir array")) + && canReadFiles(Arrays.asList(dirs), message)); + } + + /** + * May fail with any of the messages + *
  • {null check} files
  • + *
  • "#: not a File {file}"
  • + *
  • {canRead} {message}[#}
  • + */ + public boolean canReadFiles(Collection dirs, String message) { + if (nullcheck((Object) dirs, message + " files")) { + int i = 0; + for (Iterator iter = dirs.iterator(); iter.hasNext();) { + Object o = iter.next(); + if (! (o instanceof File)) { + fail(i + ": not a file " + o); + } + if (!canRead((File) o, message + "[" + i++ + "]")) { + return false; + } + } + return true; + } + return false; + } + /** + * May fail with any of the messages + *
  • {null check} dir array
  • + *
  • "#: not a File {file}"
  • + *
  • {canReadDir} {message}[#}
  • + */ + public boolean canReadDirs(Object[] dirs, String message) { + return ((nullcheck((Object) dirs, message + " dir array")) + && canReadDirs(Arrays.asList(dirs), message)); + } + + /** + * May fail with any of the messages + *
  • {null check} dirs
  • + *
  • "#: not a File {file}"
  • + *
  • {canReadDir} {message}[#}
  • + */ + public boolean canReadDirs(Collection dirs, String message) { + if (nullcheck((Object) dirs, message + " dirs")) { + int i = 0; + for (Iterator iter = dirs.iterator(); iter.hasNext();) { + Object o = iter.next(); + if (! (o instanceof File)) { + fail(i + ": not a file " + o); + } + if (!canReadDir((File) o, message + "[" + i++ + "]")) { + return false; + } + } + return true; + } + return false; + } + + /** + * May fail with any of the messages + *
  • {null check} dir array
  • + *
  • "#: not a File {file}"
  • + *
  • {canWrite} {message}[#}
  • + */ + public boolean canWriteFiles(Object[] dirs, String message) { + return ((nullcheck((Object) dirs, message + " dir array")) + && canWriteFiles(Arrays.asList(dirs), message)); + } + + /** + * May fail with any of the messages + *
  • {null check} files
  • + *
  • "#: not a File {file}"
  • + *
  • {canWrite} {message}[#}
  • + */ + public boolean canWriteFiles(Collection dirs, String message) { + if (nullcheck((Object) dirs, message + " files")) { + int i = 0; + for (Iterator iter = dirs.iterator(); iter.hasNext();) { + Object o = iter.next(); + if (! (o instanceof File)) { + fail(i + ": not a file " + o); + } + if (!canWrite((File) o, message + "[" + i++ + "]")) { + return false; + } + } + return true; + } + return false; + } + + /** + * May fail with any of the messages + *
  • {null check} dir array
  • + *
  • "#: not a File {file}"
  • + *
  • {canWriteDir} {message}[#}
  • + */ + public boolean canWriteDirs(Object[] dirs, String message) { + return ((nullcheck((Object) dirs, message + " dir array")) + && canWriteDirs(Arrays.asList(dirs), message)); + } + + /** + * May fail with any of the messages + *
  • {null check} dirs
  • + *
  • "#: not a File {file}"
  • + *
  • {canWriteDir} {message}[#}
  • + */ + public boolean canWriteDirs(Collection dirs, String message) { + if (nullcheck((Object) dirs, message + " dirs")) { + int i = 0; + for (Iterator iter = dirs.iterator(); iter.hasNext();) { + Object o = iter.next(); + if (! (o instanceof File)) { + fail(i + ": not a file " + o); + } + if (!canWriteDir((File) o, message + "[" + i++ + "]")) { + return false; + } + } + return true; + } + return false; + } + + /** + * Send an info message to any underlying handler + * @param message ignored if null + */ + public void info(String message) { + if (null != message) { + IMessageHandler handler = getHandler(); + MessageUtil.info(handler, message); + } + } + + /** Fail via message or AbortException */ + public void fail(String message) { + fail(message, (Throwable) null); + } + + /** + * Fail via message or AbortException. + * All failure messages go through here, + * so subclasses may override to control + * failure-handling. + */ + public void fail(String message, Throwable thrown) { + if ((null == message) && (null == thrown)) { + message = ""; + } + IMessage m = MessageUtil.fail(message, thrown); + if (abortOnFailure) { + throw new AbortException(m); + } else { + IMessageHandler handler = getHandler(); + handler.handleMessage(m); + } + } + + /** + * Register a file temporary, i.e., to be + * deleted on completion. The file need not + * exist (yet or ever) and may be a duplicate + * of existing files registered. + */ + public void registerTempFile(File file) { + if (null != file) { + tempFiles.add(file); + } + } + + /** + * Get a writable {possibly-empty} directory. + * If the input dir is null, then try to create a temporary + * directory using name. + * If the input dir is not null, this tries to + * create it if it does not exist. + * Then if name is not null, + * it tries to get a temporary directory + * under this using name. + * If name is null, "Validator" is used. + * If deleteContents is true, this will try to delete + * any existing contents. If this is unable to delete + * the contents of the input directory, this may return + * a new, empty temporary directory. + * If register is true, then any directory returned is + * saved for later deletion using deleteTempDirs(), + * including the input directory. + * When this is unable to create a result, if failMessage + * is not null then this will fail; otherwise it returns false; + */ + public File getWritableDir(File dir, String name, + boolean deleteContents, boolean register, String failMessage) { + // check dir + if (null == dir) { + if (null == name) { + name = "Validator"; + } + dir = FileUtil.getTempDir(name); + } else { + if (!dir.exists()) { + dir.mkdirs(); + } + } + // fail if necessary + if ((null == dir) || (!dir.exists())) { + if (null != failMessage) { + fail(failMessage + ": unable to get parent " + dir); + } + } else { + File result = FileUtil.makeNewChildDir(dir, name); + if (deleteContents) { + FileUtil.deleteContents(dir); + } + if (register) { + tempFiles.add(dir); + } + } + return dir; + } + + /** + * Delete any temp sandboxes, files or directories saved, + * optionally reporting failures in the normal way. + * This will be ignored unless the failure policy + * is unlocked. + */ + public void deleteTempFiles(boolean reportFailures) { + if (null == locker) { + for (ListIterator iter = tempFiles.listIterator(); iter.hasNext();) { + deleteFile((File) iter.next(), reportFailures); + } + for (ListIterator iter = sandboxes.listIterator(); iter.hasNext();) { + Sandbox sandbox = (Sandbox) iter.next(); + // XXX assumes all dirs are in sandboxDir + deleteFile(sandbox.sandboxDir, reportFailures); + } + } + } + + /** + * Manage temp files and directories of registered sandboxes. + * Note that a sandbox may register before it is initialized, + * so this must do nothing other than save the reference. + * @param sandbox the uninitialized Sandbox to track + */ + public void registerSandbox(Sandbox sandbox) { + sandboxes.add(sandbox); + } + + + private void deleteFile(File file, boolean reportFailures) { + if (null == file) { + if (reportFailures) { + fail("unable to delete null file"); + } + return; + } + FileUtil.deleteContents(file); + if (file.exists()) { + file.delete(); + } + if (reportFailures && file.exists()) { + fail("unable to delete " + file); + } + } + + /** @throws IllegalStateException if handler is null */ + private IMessageHandler getHandler() { + IMessageHandler handler = (IMessageHandler) handlers.peek(); + if (null == handler) { + throw new IllegalStateException("no handler"); + } + return handler; + } + +} // class Validator + diff --git a/testing/src/org/aspectj/testing/run/IRun.java b/testing/src/org/aspectj/testing/run/IRun.java new file mode 100644 index 000000000..8cc33f619 --- /dev/null +++ b/testing/src/org/aspectj/testing/run/IRun.java @@ -0,0 +1,62 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +package org.aspectj.testing.run; + +/** + * A run is a Runnable that may know how to set its own status. + * @author isberg + */ +public interface IRun { + public static final IRun[] RA_IRun = new IRun[0]; + + /** Positive wrapper for the status parameter */ + public static final IRun OK + = new IRun() { + /** This returns false when the status is null + * or runResult is false */ + public boolean run(IRunStatus status) { + return ((null != status) && status.runResult()); + } + public IRunStatus makeStatus() { return null; } + }; + + /** Negative wrapper for the status parameter */ + public static final IRun NOTOK + = new IRun() { + public boolean run(IRunStatus status) { + return ((null == status) || !status.runResult()); + } + public IRunStatus makeStatus() { return null; } + }; + + /** + * Run process, setting any known status. + * Normally the caller starts the process + * and the callee completes it, so that + * status.isCompleted() returns true after + * the call completes. However, responsible + * callees ensure starting, and responsible + * callers ensure completed after the call. + * Anyone setting completion should ensure it + * is set recursively for all children, + * and anyone starting child runs should + * ensure children are registered and initialized + * appropriately. + * @param status the IRunStatus representing the + * outcome of the process (collecting parameter). + * @see Runners + */ + boolean run(IRunStatus status) throws Exception; // IMessageHandler? +} diff --git a/testing/src/org/aspectj/testing/run/IRunIterator.java b/testing/src/org/aspectj/testing/run/IRunIterator.java new file mode 100644 index 000000000..34bc12d76 --- /dev/null +++ b/testing/src/org/aspectj/testing/run/IRunIterator.java @@ -0,0 +1,62 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +package org.aspectj.testing.run; + +import org.aspectj.bridge.IMessageHandler; + +/** + * Iterator for IRun. + * IRunIterator are useful if the underlying components + * can use a generic IRunStatus and a single listener. + * It is a requirement of any component runnin the IRunIterator + * that they call iterationCompleted() when done, to permit + * the IRunIterator to clean up. + * @see Runner#runIterator(IRunIterator, IRunStatus, IRunListener) + */ +public interface IRunIterator { + + /** + * @return true if nextRun() would return something non-null + * @throws IllegalStateException if called after + * iterationCompleted() + */ + boolean hasNextRun(); + + /** + * Get the next run. + * IRunIterator which contains child IRunIterator may either return + * the children IRun or wrap them using + * Runner.wrap(IRunIterator, IRunListener) + * @param handler the IMessageHandler to use for error and other messages + * @param runnere the Runner to use to wrap any nested IRunIterator as IRun. + * @return the next run, or null if there are no more. + * @throws IllegalStateException if called after + * iterationCompleted() + * @see Runner#wrap(IRunIterator, IRunListener) + */ + IRun nextRun(IMessageHandler handler, Runner runner); + + /** + * Signal a runner that further runs should be aborted. Runners + * should check this after each failure. + * @return true if the runner should stop iterating when an IRun fails + * @throws IllegalStateException if called after + * iterationCompleted() + */ + boolean abortOnFailure(); // XXX supply IRun or IRunStatus? + + /** called when hasNextRun() and nextRun() will no longer be called */ + void iterationCompleted(); +} diff --git a/testing/src/org/aspectj/testing/run/IRunListener.java b/testing/src/org/aspectj/testing/run/IRunListener.java new file mode 100644 index 000000000..85ab6148b --- /dev/null +++ b/testing/src/org/aspectj/testing/run/IRunListener.java @@ -0,0 +1,38 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +package org.aspectj.testing.run; + +/** + * Listen to events in the run lifecycle - + * birth, death, and procreation. + * @author isberg + */ +public interface IRunListener { + + /** + * Called when run is about to be started. + */ + void runStarting(IRunStatus run); + + /** + * Called when run is has completed. + */ + void runCompleted(IRunStatus run); + + /** + * Called when adding a child to a parent run + */ + void addingChild(IRunStatus parent, IRunStatus child); +} diff --git a/testing/src/org/aspectj/testing/run/IRunStatus.java b/testing/src/org/aspectj/testing/run/IRunStatus.java new file mode 100644 index 000000000..6651c42f1 --- /dev/null +++ b/testing/src/org/aspectj/testing/run/IRunStatus.java @@ -0,0 +1,175 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +package org.aspectj.testing.run; + +import org.aspectj.bridge.IMessage; +import org.aspectj.bridge.IMessageHolder; +import org.aspectj.bridge.MessageUtil; + +/** + * Encapsulate status and results for a run. + * A run starts and then completes normally + * (finished(Object result)), + * abruptly (thrown(Throwable thrown)), + * or by user request (abort(Object request)). + * @author isberg + */ +public interface IRunStatus extends IMessageHolder { + /** clients use this when signalling completion without a specific result */ + public static final Object VOID = Boolean.TRUE; + + /** result object for successful (unset) boolean run result */ + public static final Boolean PASS = Boolean.TRUE; + + /** result object for failed (unset) boolean run result */ + public static final Boolean FAIL = Boolean.FALSE; + + /** clients use this when signalling abort without any specific request */ + public static final Object ABORT = Boolean.FALSE; + + /** clients use this when signalling abort because no object to run */ + public static final Object ABORT_NORUN = MessageUtil.ABORT_NOTHING_TO_RUN; + + /** returned from getChildren when there are no children */ + public static final IRunStatus[] EMPTY_NEST = new IRunStatus[0]; + + //------------------- process controls + /** + * Set identifier associated with this run, if any + * @throws IllegalArgumentException if id is null + * @throws IllegalStateException if id has already been set + */ + void setIdentifier(Object id); + + //------------------- process controls + /** + * Call before any start() or after isCompleted() would return true + * to reset this to its pre-start state + * @throws IllegalStateException if start() has been called + * and isCompleted() is not true. + */ + void reset(); + + /** + * Call only once to signal this run has started. + * @throws IllegalStateException if start() has been called + */ + void start(); + + /** + * Call this or thrown only once after start() + * to signal this run has ended. + * If this represents a void process, use VOID. + * @param result the Object returned by this run. + * @throws IllegalStateException if start() was not called first + * or if either completed(Object) or thrown(Throwable) have been called. + */ + void finish(Object result); + + /** + * Call to signal this run is ending by request. + * If there is no message, use ABORT. + * @param request the Object request to abort, + * or ABORT if none is available. + * @throws IllegalStateException if start() was not called first + * or if either completed(Object) or thrown(Throwable) have been called. + */ + void abort(Object request); + + /** + * Call this or completed only once after start() + * to signal this run has ended. + * @throws IllegalStateException if start() was not called first + * or if either completed(Object) or thrown(Throwable) have been called. + */ + void thrown(Throwable thrown); + + + /** + * Call this for the status to throw an unchecked exception + * of the type that its controller understands. + * It is an error for a IRunStatus to continue normally + * after this is invoked. + */ + void completeAbruptly(); + //------------------- process messages + /** + * Call this any time to signal any messages. + * (In particular, the IRun caller may use this to register messages + * about the mishandling of the run by the ResultStatusI by the callee.) + * This is a shortcut for getMessageHandler().handleMessage(..); + */ + //boolean handleMessage(IMessage message); + + //------------------- process display + /** @return true if this run has started */ + boolean started(); + + /** @return true if one of the result, abort request, or thrown is available */ + boolean isCompleted(); + + /** @return true if this got an abort request */ + boolean aborted(); + + /** + * @return true if completed and not aborted and no thrown + * or messages with kind ABORT or FAIL or ERROR + */ + boolean runResult(); + + /** get the invoker for any subruns */ + Runner getRunner(); + + /** @return the Object result, if any, of this run */ + Object getResult(); + + /** @return the Object abort request, if any, of this run */ + Object getAbortRequest(); + + /** @return the Throwable thrown, if any, by this run */ + Throwable getThrown(); + + /** @return any Message[] signalled, or SILENCE if none */ + IMessage[] getMessages(); + + /** @return the identifier set for this run, if any */ + Object getIdentifier(); + + //------------------- subprocess + /** + * Add a record for a child run + * and install self as parent. + * @throws IllegalArgumentException if child is null + */ + void addChild(IRunStatus child); + + /** + * Register this as the run parent. + * (Any run that does addChild(IRunStatus) should register as parent.) + * @throws IllegalArgumentException if parent is null + * @throws IllegalStateException if parent exists already + */ + void registerParent(IRunStatus parent); + + /** + * @return the current children of this run, or EMPTY_NEST if none + */ + IRunStatus[] getChildren(); + + /** + * @return the currently-registered parent, or null if none + */ + IRunStatus getParent(); +} diff --git a/testing/src/org/aspectj/testing/run/IRunValidator.java b/testing/src/org/aspectj/testing/run/IRunValidator.java new file mode 100644 index 000000000..29f73f103 --- /dev/null +++ b/testing/src/org/aspectj/testing/run/IRunValidator.java @@ -0,0 +1,28 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +package org.aspectj.testing.run; + +/** + * These check whether particular runs have passed. + * @author isberg + */ +public interface IRunValidator { + /** + * Evaluate whether a run has passed. + * @param run the IRunStatus to see if it passed. + * @return true if run has passed + */ + boolean runPassed(IRunStatus run); +} diff --git a/testing/src/org/aspectj/testing/run/RunIterator.java b/testing/src/org/aspectj/testing/run/RunIterator.java new file mode 100644 index 000000000..37e3187ed --- /dev/null +++ b/testing/src/org/aspectj/testing/run/RunIterator.java @@ -0,0 +1,136 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +package org.aspectj.testing.run; + +import org.aspectj.bridge.IMessageHandler; +import org.aspectj.bridge.MessageUtil; +import org.aspectj.util.LangUtil; + +import java.util.Arrays; +import java.util.List; +import java.util.ListIterator; + +/** + * Adapt IRun or Run[] or List or ListIterator to RunIteratorI. + */ +public class RunIterator implements IRunIterator { + + protected String name; + protected ListIterator iter; + protected IRun run; + + public RunIterator(String name, IRun run) { + init(name, run); + } + + public RunIterator(String name, List list) { + init(name, list); + } + + public RunIterator(String name, IRun[] runs) { + init(name, Arrays.asList(runs).listIterator()); + } + + public RunIterator(String name, ListIterator iterator) { + init(name, iterator); + } + + public void init(String name, List list) { + init(name, list.listIterator()); + } + + public void init(String name, IRun[] runs) { + init(name, Arrays.asList(runs).listIterator()); + } + + /** @return true if the first IRun from nextRun can be the sole IRun */ + public boolean isHoistable() { + return (null != run); + } + + /** + * @param name if null, use iterator.toString(); + * @param iterator not null + * @throws IllegalArgumentException if iterator is null + */ + public void init(String name, ListIterator iterator) { + LangUtil.throwIaxIfNull(iterator, "iterator"); + iter = iterator; + name = (null != name? name : iterator.toString()); + run = null; + } + + /** + * @param name if null, use run(); + * @param run not null + * @throws IllegalArgumentException if iterator is null + */ + public void init(String name, IRun run) { + LangUtil.throwIaxIfNull(run, "run"); + this.run = run; + name = (null != name? name : run.toString()); + iter = null; + } + + /** + * @return false always + * @see org.aspectj.testing.run.IRunIterator#abortOnFailure() + */ + public boolean abortOnFailure() { + return false; + } + + /** + * @see org.aspectj.testing.run.RunIteratorI#hasNextRun() + */ + public boolean hasNextRun() { + return ((null != run) || ((null != iter) && (iter.hasNext()))); + } + + /** + * @see org.aspectj.testing.run.IRunIterator#iterationCompleted() + */ + public void iterationCompleted() { + } + + + /** + * @see org.aspectj.testing.run.RunIteratorI#nextRun(IMessageHandler, Runner) + */ + public IRun nextRun(IMessageHandler handler, Runner runner) { + if (null != run) { + IRun result = run; + run = null; + return result; + } + if (null != iter) { + for (Object o = iter.next(); iter.hasNext();) { + if (o instanceof IRunIterator) { + return runner.wrap((IRunIterator) o, null); + } else if (o instanceof IRun) { + return (IRun) o; + } else { + MessageUtil.error(handler, "not IRun or IRunIterator: " + o); + } + } + } + return null; + } + + /** @return name */ + public String toString() { + return name; + } +} diff --git a/testing/src/org/aspectj/testing/run/RunListener.java b/testing/src/org/aspectj/testing/run/RunListener.java new file mode 100644 index 000000000..523862991 --- /dev/null +++ b/testing/src/org/aspectj/testing/run/RunListener.java @@ -0,0 +1,116 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +package org.aspectj.testing.run; + +import org.aspectj.bridge.AbortException; +import org.aspectj.bridge.MessageUtil; + +import java.io.PrintWriter; +import java.util.List; + +/** + * A generic RunListener for easier partial implementations. + * It can take a RunI selector (called on completion) + * and/or a List to accumulate complete IRunStatus + * (if the selector is null or returns true). + * It can also take a PrintWriter and String to print traces of each event + * as "{prefix} [addingChild|runStarting|runCompleted]({IRunStatus})" + */ +public class RunListener implements IRunListener { + protected final List list; + protected final IRun selector; + protected final PrintWriter writer; + protected final String prefix; + + protected RunListener() { + this((List) null, (IRun) null, (PrintWriter) null, (String) null); + } + + /** + * @param sink the List sink for any IRunStatus if the selector is null + * or returns true for run(IRunStatus) - ignored if null. + * @param selector the IRun called on completion, + * perhaps to select those to be accumulated + * (should NOT throw Exception) + */ + public RunListener(List sink, IRun selector) { + this(sink, selector, (PrintWriter) null, (String) null); + } + + /** + * @param writer the PrintWriter to print events to - may be null + * @param prefix the String prefixing any printing - if null, "" + */ + public RunListener(PrintWriter writer, String prefix) { + this((List) null, (IRun) null, writer, prefix); + } + + /** + * @param sink the List sink for any IRunStatus if the selector is null + * or returns true for run(IRunStatus) - ignored if null. + * @param selector the IRun called on completion, + * perhaps to select those to be accumulated + * (should NOT throw Exception) + * @param writer the PrintWriter to print events to - may be null + * @param prefix the String prefixing any printing - if null, "" + */ + public RunListener(List sink, IRun selector, PrintWriter writer, String prefix) { + this.prefix = (null == prefix ? "" : prefix); + this.writer = writer; + this.selector = selector; + list = sink; + } + + /** + * @see org.aspectj.testing.harness.run.IRunListener#addingChild(IRunStatus, IRunStatus) + */ + public void addingChild(IRunStatus parent, IRunStatus child) { + if (null != writer) { + writer.println(prefix + " addingChild(\"" + parent + + "\", \"" + child + "\")"); + } + } + + /** + * @see org.aspectj.testing.harness.run.IRunListener#runStarting(IRunStatus) + */ + public void runStarting(IRunStatus run) { + if (null != writer) { + writer.println(prefix + " runStarting(\"" + run + "\")"); + } + } + + /** + * Print to writer (if any), run selector (if any), and add to list + * (if any and if selector is null or returns true). + * @see org.aspectj.testing.harness.run.IRunListener#runCompleted(IRunStatus) + * @throws AbortException wrapping any Exception thrown by any selector + * (error for selector to throw Exception) + */ + public void runCompleted(IRunStatus run) { + if (null != writer) { + writer.println(prefix + " runCompleted(\"" + run + "\")"); + } + try { + if (((null == selector) || selector.run(run)) && (null != list)) { + list.add(run); + } + } catch (Throwable e) { + String m = "Selectors should not throw exceptions!"; + throw new AbortException(MessageUtil.abort(m, e)); + } + } + +} diff --git a/testing/src/org/aspectj/testing/run/RunListeners.java b/testing/src/org/aspectj/testing/run/RunListeners.java new file mode 100644 index 000000000..9b27d8605 --- /dev/null +++ b/testing/src/org/aspectj/testing/run/RunListeners.java @@ -0,0 +1,89 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +package org.aspectj.testing.run; + +import java.util.ArrayList; +import java.util.Iterator; + +/** + * Aggregate listeners into one and run synchronously in order added. + * @author isberg + */ +public class RunListeners extends RunListener implements IRunListener { + + ArrayList listeners; + public RunListeners() { + listeners = new ArrayList(); + } + + public void addListener(IRunListener listener) { + if (null != listener) { + listeners.add(listener); + } + } + + public void removeListener(IRunListener listener) { + if (null != listener) { + listeners.remove(listener); + } + } + + + /** + * Run all listeners with the given status. + * @see org.aspectj.testing.harness.run.IRunListener#runStarting(IRunStatus) + */ + public final void runStarting(IRunStatus status) { + if (null == status) { + throw new IllegalArgumentException("null RunStatusI"); + } + Iterator iter = listeners.iterator(); + while(!status.aborted() && iter.hasNext()) { + IRunListener element = (IRunListener) iter.next(); + element.runStarting(status); + } + } + + /** + * Signal all listeners with the given status. + * @see org.aspectj.testing.harness.run.IRunListener#runCompleted(IRunStatus) + */ + public final void runCompleted(IRunStatus status) { + if (null == status) { + throw new IllegalArgumentException("null RunStatusI"); + } + Iterator iter = listeners.iterator(); + while(!status.aborted() && iter.hasNext()) { + IRunListener element = (IRunListener) iter.next(); + element.runCompleted(status); + } + } + /** + * @see org.aspectj.testing.harness.run.IRunListener#addingChild(IRunStatus, IRunStatus) + */ + public final void addingChild(IRunStatus parent, IRunStatus child) { + if (null == child) { + throw new IllegalArgumentException("null child"); + } + if (null == parent) { + throw new IllegalArgumentException("null parent"); + } + Iterator iter = listeners.iterator(); + while(!parent.aborted() && ! child.aborted() && iter.hasNext()) { + IRunListener element = (IRunListener) iter.next(); + element.addingChild(parent, child); + } + } +} diff --git a/testing/src/org/aspectj/testing/run/RunStatus.java b/testing/src/org/aspectj/testing/run/RunStatus.java new file mode 100644 index 000000000..0a53080e1 --- /dev/null +++ b/testing/src/org/aspectj/testing/run/RunStatus.java @@ -0,0 +1,408 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +package org.aspectj.testing.run; + +import org.aspectj.bridge.IMessage; +import org.aspectj.bridge.IMessageHolder; +import org.aspectj.bridge.MessageHandler; +import org.aspectj.testing.util.BridgeUtil; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * Default implementation of {@link IRunStatus}. + * @author isberg + */ +public class RunStatus implements IRunStatus { + private static int INDEX; + + private final String name = "RunStatus[" + INDEX++ +"]"; + + /** true after isCompleted() evaluated true */ + private boolean evaluated; + + /** true after starting() called */ + private boolean started; // set only in starting() + + /** true after finished(int) or thrown(Throwable) called */ + private boolean completed; // set only in completed(boolean) + + /** contains any id set */ + private Object id; + + /** after finished(Object) called, contains that parameter */ + private Object result; + + /** after aborted(Object) called, contains that parameter */ + private Object abortRequest; + + /** use to set exception thrown, if any */ + private Throwable thrown; + + /** list of any messages submitted */ + private IMessageHolder messageHolder; + + /** list of any child status */ + private ArrayList children; + + /** parent of this status */ + private IRunStatus parent; + + /** invoker for any subruns */ + private Runner runner; + + /** controls runResult() */ + private IRunValidator validator; + +// public RunStatus() { +// reset(); +// validator = RunValidator.NORMAL; +// } + + public RunStatus(IMessageHolder holder, Runner runner) { + reset(holder, runner); + validator = RunValidator.NORMAL; + } + + //------------------- process controls + + /** + * Set identifier associated with this run, if any + * @throws IllegalArgumentException if id is null + * @throws IllegalStateException if id has already been set + */ + public void setIdentifier(Object id) { + if (null == id) { + throw new IllegalArgumentException("null id"); + } else if ((null != this.id) && (id != this.id)) { + throw new IllegalStateException( + "attempt to set id " + this.id + " to " + id); + } + this.id = id; + } + + /** + * Set the current validator. + * @param delegate the RunValidatorI to use when calculating runStatus + * @throws IllegalArgumentException if delegate is null + */ + public void setValidator(IRunValidator delegate) { + if (null == delegate) { + throw new IllegalArgumentException("null delegate"); + } + if (validator != delegate) { + validator = delegate; + } + } + + /** + * Call before any start() or after isCompleted() would return true + * to reset this to its pre-start state + * @throws IllegalStateException if start() has been called + * and isCompleted() is not true. + */ + public void reset() { + reset((IMessageHolder) null, (Runner) null); + } + + /** + * Call before any start() or after isCompleted() would return true + * to reset this to its pre-start state. Does not affect validator. + * @param holder the IMessageHolder to use after resetting. + * @throws IllegalStateException if start() has been called + * and isCompleted() is not true. + */ + public void reset(IMessageHolder holder, Runner runner) { + if (null == runner) { + throw new IllegalArgumentException("null runner"); + } + if (started && (!isCompleted())) { + throw new IllegalStateException("no reset() until isCompleted"); + } + started = false; + completed = false; + result = null; + abortRequest = null; + thrown = null; + parent = null; + id = null; + messageHolder = (null != holder ? holder : new MessageHandler()); + if (null != children) { + children.clear(); + } + this.runner = runner; + evaluated = false; + } + + /** + * Call only once to signal this run has started. + * @throws IllegalStateException if start() has been called + */ + public void start() { + if (started) { + throw new IllegalStateException("started already"); + } else if (isCompleted()) { + throw new IllegalStateException("start after completed (do reset)"); + } + started = true; + } + + /** + * Call this or thrown only once after start() + * to signal this run has ended. + * If this represents a void process, use VOID. + * @param result the Object returned by this run. + * @throws IllegalStateException if start() was not called first + * or if either completed(Object) or thrown(Throwable) have been called. + */ + public void finish(Object result) { + if (null == result) { + throw new IllegalArgumentException("null result"); + } else if (isCompleted()) { + throw new IllegalStateException( + "completed then finish " + result); + } + this.result = result; + } + + /** + * Call to signal this run is ending by request. + * If this represents a void process, use VOID. + * If there is no message, use ABORT. + * @param request the Object request to abort, + * or ABORT if none is available. + * @throws IllegalStateException if start() was not called first + * or if either completed(Object) or thrown(Throwable) have been called. + */ + public void abort(Object request) { + if (null == request) { + throw new IllegalArgumentException("null request"); + } else if (isCompleted()) { + throw new IllegalStateException( + "completed then abort " + request); + } + this.abortRequest = request; + } + + /** + * Call this or completed only once after start() + * to signal this run has ended. + * @throws IllegalStateException if start() was not called first + * or if either completed(Object) or thrown(Throwable) have been called. + */ + public void thrown(Throwable thrown) { + if (null == thrown) { + throw new IllegalArgumentException("null thrown"); + } else if (isCompleted()) { + throw new IllegalStateException( + "completed then thrown " + thrown); + } + this.thrown = thrown; + } + + public void completeAbruptly() { + throw new Error("completing abruptly"); // XXX configurable + } + + /** + * @return true if completed, not aborted, no thrown, no + * messages of kind ERROR, FAIL or ABORT, and + * result object is not IRunStatus.FAIL. + * @see org.aspectj.testing.harness.newbridge.IRunStatus#runResult() + */ + public boolean runResult() { + return validator.runPassed(this); + } + + //------------------- process messages + /** + * Call this any time before isCompleted() would return true + * to signal any messages. + * @throws IllegalStateException if isCompleted(). + */ + public boolean handleMessage(IMessage message) { + return messageHolder.handleMessage(message); + } + public boolean isIgnoring(IMessage.Kind kind) { + return messageHolder.isIgnoring(kind); + } + + /** + * @see org.aspectj.bridge.IMessageHolder#hasAnyMessage(Kind, boolean) + */ + public boolean hasAnyMessage(IMessage.Kind kind, boolean orGreater) { + return messageHolder.hasAnyMessage(kind, orGreater); + } + + /** + * @see org.aspectj.bridge.IMessageHolder#getMessages(Kind) + */ + public IMessage[] getMessages(IMessage.Kind kind, boolean orGreater) { + return messageHolder.getMessages(kind, orGreater); + } + + /** + * @see org.aspectj.bridge.IMessageHolder#numMessages(Kind) + */ + public int numMessages(IMessage.Kind kind, boolean orGreater) { + return messageHolder.numMessages(kind, orGreater); + } + + //------------------- process display + /** @return true if this run has started */ + public boolean started() { + return started; + } + + /** @return true if one of the result, abort request, or thrown is available */ + public boolean isCompleted() { + if (!evaluated) { + if (started + && ((null != thrown) + || (null != result) + || (null != abortRequest))) { + completed = true; + evaluated = true; + } + } + return completed; + } + + /** @return true if this got an abort request */ + public boolean aborted() { + return (completed && (null != abortRequest)); + } + + /** @return the Object result, if any, of this run */ + public Object getResult() { + return result; + } + + /** @return the Object abort request, if any, of this run */ + public Object getAbortRequest() { + return abortRequest; + } + + /** @return the Throwable thrown, if any, by this run */ + public Throwable getThrown() { + return thrown; + } + + /** + * @see org.aspectj.bridge.IMessageHolder#getUnmodifiableListView() + */ + public List getUnmodifiableListView() { + return messageHolder.getUnmodifiableListView(); + } + + /** @return any Message[] signalled, or IMessage.NONE if none */ + public IMessage[] getMessages() { + return messageHolder.getMessages(null, IMessageHolder.EQUAL); + } + + /** @return the identifier set for this run, if any */ + public Object getIdentifier() { + return id; + } + + /** + * @see org.aspectj.bridge.IMessageHolder#clearMessages() + * @throws UnsupportedOperationException always + */ + public void clearMessages() throws UnsupportedOperationException { + throw new UnsupportedOperationException("use reset"); + } + + //------------------- subprocess + + /** get the invoker for any subrunners */ + public Runner getRunner() { + return runner; + } + + /** + * Add a record for a child run + * and install self as parent. + * @throws IllegalArgumentException if child is null + */ + public void addChild(IRunStatus child) { + if (null == child) { + throw new IllegalArgumentException("null child"); + } + if (null == children) { + children = new ArrayList(); + } + children.add(child); + } + + /** + * Register this as the run parent. + * (Any run that does addChild(IRunStatus) should register as parent.) + * @throws IllegalArgumentException if parent is null + * @throws IllegalStateException if parent exists already + */ + public void registerParent(IRunStatus parent) { + if (null == parent) { + throw new IllegalArgumentException("null parent"); + } else if (null != this.parent) { + throw new IllegalStateException( + "adding parent " + parent + " to parent " + this.parent); + } + this.parent = parent; + } + + /** + * @return the current children of this run, or EMPTY_NEST if none + */ + public IRunStatus[] getChildren() { + if ((null == children) || (0 == children.size())) { + return EMPTY_NEST; + } else { + return (IRunStatus[]) children.toArray(EMPTY_NEST); + } + } + + /** + * @return the currently-registered parent, or null if none + */ + public IRunStatus getParent() { + return parent; + } + + public String toString() { + return BridgeUtil.toShortString(this); + } + + public String toLongString() { + StringBuffer sb = new StringBuffer(); + sb.append(BridgeUtil.toShortString(this)); + if ((null != children) && (0 < children.size())) { + String label = "### --------- " + name; + int index = 0; + for (Iterator iter = children.iterator(); iter.hasNext();) { + IRunStatus childStatus = (IRunStatus) iter.next(); + String childLabel = + "\n" + label + " child[" + index++ +"] " + + childStatus.getIdentifier(); + sb.append(childLabel + " ---- start\n"); + sb.append(childStatus.toString()); + sb.append(childLabel + " ---- end\n"); + } + } + return sb.toString(); + } +} diff --git a/testing/src/org/aspectj/testing/run/RunValidator.java b/testing/src/org/aspectj/testing/run/RunValidator.java new file mode 100644 index 000000000..9cf91e37a --- /dev/null +++ b/testing/src/org/aspectj/testing/run/RunValidator.java @@ -0,0 +1,206 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +package org.aspectj.testing.run; + +import org.aspectj.bridge.IMessage; +import org.aspectj.bridge.IMessageHolder; +import org.aspectj.testing.util.IntRange; +import org.aspectj.testing.util.ObjectChecker; + +/** + * This checks if a run status passes, as follows: + *
  • state: fail unless completed and not aborted
  • + *
  • messages: fail if any of type ABORT, FAIL - permit ERROR, WARNING, DEBUG... + * (which permits expected compiler errors and warnings)
  • + *
  • thrown: if type required, fail unless type thrown; + * if type permitted, fail if wrong type thrown
  • + *
  • result: fail unless no ObjectChecker or it validates + * and the result is not IRunStatus.FAIL.
  • + *
  • otherwise delegates to any subclass doPassed()
  • + * Client setup the expected and permitted exception classes + * and the result object checker, and may also subclass to + * query the IRunStatus more carefully. + *

    + * Note that IRunStatus states can be out of sync with messages, + * e.g., as underlying components signal ABORT without using abort(...). + */ +public class RunValidator implements IRunValidator { + /** expect normal completion with any non-null result object */ + public static final IRunValidator NORMAL + = new RunValidator(ObjectChecker.ANY); + + /** expect normal completion and Integer result object with value 0 */ + public static final IRunValidator ZERO_STATUS + = new RunValidator(IntRange.ZERO); + + /** expect finished(IRunStatus.PASS) and no thrown, fail, etc. */ + public static final IRunValidator PASS + = new RunValidator(new ObjectChecker() { + public boolean isValid(Object o) { + return (o == IRunStatus.PASS); + } + }); + + /** expect finished(IRunStatus.FAIL) */ + public static final IRunValidator FAIL + = new RunValidator(new ObjectChecker() { + public boolean isValid(Object o) { + return (o == IRunStatus.FAIL); + } + }); + + /** range of status values required for passing */ + private ObjectChecker resultChecker; + + // XXX replace two exc. classes with one, plus boolean for how to interpret? + /** if non-null, passed() permits any thrown assignable to this class */ + private Class permittedExceptionsClass; + + /** if non-null, passed() requires some thrown assignable to this class */ + private Class requiredExceptionsClass; + + /** Create result validator that expects a certain int status */ + public RunValidator(ObjectChecker resultChecker) { + this(resultChecker, null, null); + } + + /** + * Create result validator that passes only when completed abruptly by + * a Throwable assignable to the specified class. + * @throws illegalArgumentException if requiredExceptionsClass is not Throwable + */ + public RunValidator(Class requiredExceptionsClass) { + this(null, null, requiredExceptionsClass); + } + + /** + * Create a result handler than knows how to evaluate {@link #passed()}. + * You cannot specify both permitted and required exception class, + * and any exception class specified must be assignable to throwable. + * + * @param resultChecker {@link #passed()} will return false if + * the int status is not accepted by this int validator - if null, + * any int status result is accepted. + * @param fastFailErrorClass an Error subclass with a (String) constructor to use to + * construct and throw Error from fail(String). If null, then fail(String) + * returns normally. + * @param permittedExceptionsClass if not null and any exceptions thrown are + * assignable to this class, {@link #passed()} will not return + * false as it normally does when exceptions are thrown. + * @param requiredExceptionsClass if not null, {@link #passed()} will return false + * unless some exception was thrown that is assignable to this class. + * @throws illegalArgumentException if any exception class is not Throwable + * or if fast fail class is illegal (can't make String constructor) + */ + protected RunValidator( + ObjectChecker resultChecker, + Class permittedExceptionsClass, + Class requiredExceptionsClass) { + init(resultChecker,permittedExceptionsClass, requiredExceptionsClass); + } + + /** same as init with existing values */ + protected void reset() { + init(resultChecker, permittedExceptionsClass, + requiredExceptionsClass); + } + + /** subclasses may use this to re-initialize this for re-use */ + protected void init( + ObjectChecker resultChecker, + Class permittedExceptionsClass, + Class requiredExceptionsClass) { + this.permittedExceptionsClass = permittedExceptionsClass; + this.requiredExceptionsClass = requiredExceptionsClass; + + if (null != resultChecker) { + this.resultChecker = resultChecker; + } else { + this.resultChecker = IntRange.ANY; + } + + if (null != permittedExceptionsClass) { + if (!Throwable.class.isAssignableFrom(permittedExceptionsClass)) { + String e = "permitted not throwable: " + permittedExceptionsClass; + throw new IllegalArgumentException(e); + } + } + if (null != requiredExceptionsClass) { + if (!Throwable.class.isAssignableFrom(requiredExceptionsClass)) { + String e = "required not throwable: " + requiredExceptionsClass; + throw new IllegalArgumentException(e); + } + } + if ((null != permittedExceptionsClass) + && (null != requiredExceptionsClass) ) { + String e = "define at most one of required or permitted exceptions"; + throw new IllegalArgumentException(e); + } + } + + /** @return true if this result passes per this validator */ + public final boolean runPassed(IRunStatus result) { + if (null == result) { + throw new IllegalArgumentException("null result"); + } + // After the result has completed, the result is stored. + if (!result.isCompleted()) { + return false; + } + if (result.aborted()) { + return false; + } + if (null != result.getAbortRequest()) { + return false; + } + Object resultObject = result.getResult(); + if (!resultChecker.isValid(resultObject)) { + return false; + } + if (resultObject == IRunStatus.FAIL) { + return false; + } + // need MessageHandler.getMessage(...) + if (result.hasAnyMessage(IMessage.FAIL, IMessageHolder.ORGREATER)) { + return false; + } + Throwable thrown = result.getThrown(); + if (null == thrown) { + if (null != requiredExceptionsClass) { + return false; + } + } else { + Class c = thrown.getClass(); + // at most one of the ExceptionsClass set + if (null != requiredExceptionsClass) { + if (!requiredExceptionsClass.isAssignableFrom(c)) { + return false; + } + } else if (null != permittedExceptionsClass) { + if (!permittedExceptionsClass.isAssignableFrom(c)) { + return false; + } + } else { + return false; + } + } + return dopassed(); + } + + /** subclasses implement subclass-specific behavior for passed() here */ + protected boolean dopassed() { + return true; + } +} diff --git a/testing/src/org/aspectj/testing/run/Runner.java b/testing/src/org/aspectj/testing/run/Runner.java new file mode 100644 index 000000000..6cb9e75fa --- /dev/null +++ b/testing/src/org/aspectj/testing/run/Runner.java @@ -0,0 +1,510 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +package org.aspectj.testing.run; + +import org.aspectj.bridge.IMessage; +import org.aspectj.bridge.IMessageHandler; +import org.aspectj.bridge.IMessageHolder; +import org.aspectj.bridge.Message; +import org.aspectj.bridge.MessageHandler; +import org.aspectj.bridge.MessageUtil; +import org.aspectj.util.LangUtil; + +import java.util.Enumeration; +import java.util.Hashtable; + +/** + * Run IRun, setting status and invoking listeners + * for simple and nested runs. + *

    + * This manages baseline IRun status reporting: + * any throwables are caught and reported, and + * the status is started before running and + * (if not already completed) completed after. + *

    + * This runs any IRunListeners specified directly in the + * run*(..., IRunListener) methods + * as well as any specified indirectly by registering listeners per-type + * in {@link registerListener(Class, IRunListener)} + *

    + * For correct handling of nested runs, this sets up + * status parent/child relationships. + * It uses the child result object supplied directly in the + * runChild(..., IRunStatus childStatus,..) methods, + * or (if that is null) one obtained from the child IRun itself, + * or (if that is null) a generic IRunStatus. + *

    + * For IRunIterator, this uses IteratorWrapper to wrap the + * iterator as an IRun. Runner and IteratorWrapper coordinate + * to handle fast-fail (aborting further iteration when an IRun fails). + * The IRunIterator itself may specify fast-fail by returning true + * from {@link IRunIterator#abortOnFailure()}, or clients can + * register IRunIterator by Object or type for fast-failure using + * {@link registerFastFailIterator(IRunIterator)} or + * {@link registerFastFailIterator(Class)}. + * This also ensures that + * {@link IRunIterator#iterationCompleted()} is + * called after the iteration process has completed. + */ +public class Runner { + // XXX need to consider wiring in a logger - sigh + + private static final IMessage FAIL_NORUN + = MessageUtil.fail("Null IRun parameter to Runner.run(IRun..)"); + private static final IMessage FAIL_NORUN_ITERATOR + = MessageUtil.fail("Null IRunterator parameter to Runner.run(IRunterator...)"); + + public Runner() { + } + + /** + * Do the run, setting run status, invoking + * listener, and aborting as necessary. + * If the run is null, the status is + * updated, but the listener is never run. + * If the listener is null, then the runner does a lookup + * for the listeners of this run type. + * Any exceptions thrown by the listener(s) are added + * to the status messages and processing continues. + * unless the status is aborted. + * The status will be completed when this method completes. + * @param run the IRun to run - if null, issue a FAIL message + * to that effect in the result. + * @throws IllegalArgumentException if status is null + * @return boolean result returned from IRun + * or false if IRun did not complete normally + * or status.runResult() if aborted. + */ + /* XXX later permit null status + * If the status is null, this tries to complete + * the run without a status. It ignores exceptions + * from the listeners, but does not catch any from the run. + */ + public boolean run(IRun run, IRunStatus status, + IRunListener listener) { + return run(run, status, listener, (Class) null); + } + + public boolean run(IRun run, IRunStatus status, + IRunListener listener, Class exceptionClass) { + if (!precheck(run, status)) { + return false; + } + RunListeners listeners = getListeners(run, listener); + return runPrivate(run, status, listeners, exceptionClass); + + } + + /** + * Run child of parent, handling interceptor registration, etc. + * @throws IllegalArgumentException if parent or child status is null + */ + public boolean runChild(IRun child, + IRunStatus parentStatus, + IRunStatus childStatus, + IRunListener listener) { + return runChild(child, parentStatus, childStatus, listener, null); + } + + /** + * Run child of parent, handling interceptor registration, etc. + * If the child run is supposed to throw an exception, then pass + * the exception class. + * After this returns, the childStatus is guaranteed to be completed. + * If an unexpected exception is thrown, an ABORT message + * is passed to childStatus. + * @param parentStatus the IRunStatus for the parent - must not be null + * @param childStatus the IRunStatus for the child - default will be created if null + * @param exceptionClass the Class of any expected exception + * @throws IllegalArgumentException if parent status is null + */ + public boolean runChild(IRun child, + IRunStatus parentStatus, + IRunStatus childStatus, + IRunListener listener, + Class exceptionClass) { + if (!precheck(child, parentStatus)) { + return false; + } + if (null == childStatus) { + childStatus = new RunStatus(new MessageHandler(), this); + } + installChildStatus(child, parentStatus, childStatus); + if (!precheck(child, childStatus)) { + return false; + } + RunListeners listeners = getListeners(child, listener); + if (null != listeners) { + try { + listeners.addingChild(parentStatus, childStatus); + } catch (Throwable t) { + String m = "RunListenerI.addingChild(..) exception " + listeners; + parentStatus.handleMessage(MessageUtil.abort(m, t)); // XXX + } + } + boolean result = false; + try { + result = runPrivate(child, childStatus, listeners, exceptionClass); + } finally { + if (!childStatus.isCompleted()) { + childStatus.finish(result ? IRunStatus.PASS : IRunStatus.FAIL); + childStatus.handleMessage(MessageUtil.debug("XXX parent runner set completion")); + } + } + boolean childResult = childStatus.runResult(); + if (childResult != result) { + childStatus.handleMessage(MessageUtil.info("childResult != result=" + result)); + } + return childResult; + } + + public IRunStatus makeChildStatus(IRun run, IRunStatus parent, IMessageHolder handler) { + return installChildStatus(run, parent, new RunStatus(handler, this)); + } + + /** + * Setup the child status before running + * @param run the IRun of the child process (not null) + * @param parent the IRunStatus parent of the child status (not null) + * @param child the IRunStatus to install - if null, a generic one is created + * @return the IRunStatus child status (as passed in or created if null) + */ + public IRunStatus installChildStatus(IRun run, IRunStatus parent, IRunStatus child) { + if (null == parent) { + throw new IllegalArgumentException("null parent"); + } + if (null == child) { + child = new RunStatus(new MessageHandler(), this); + } + child.setIdentifier(run); // XXX leak if ditching run... + parent.addChild(child); + return child; + } + + /** + * Do a run by running all the subrunners provided by the iterator, + * creating a new child status of status for each. + * If the iterator is null, the result is + * updated, but the interceptor is never run. + * @param iterator the IRunteratorI for all the IRun to run + * - if null, abort (if not completed) or message to status. + * @throws IllegalArgumentException if status is null + */ + public boolean runIterator(IRunIterator iterator, IRunStatus status, + IRunListener listener) { + LangUtil.throwIaxIfNull(status, "status"); + if (status.aborted()) { + return status.runResult(); + } + if (null == iterator) { + if (!status.isCompleted()) { + status.abort(IRunStatus.ABORT_NORUN); + } else { + status.handleMessage(FAIL_NORUN); + } + return false; + } + IRun wrapped = wrap(iterator, listener); + return run(wrapped, status, listener); + } + + /** + * Signal whether to abort on failure for this run and iterator, + * based on the iterator and any fast-fail registrations. + * @param iterator the IRunIterator to stop running if this returns true + * @param run the IRun that failed + * @return true to halt further iterations + */ + private boolean abortOnFailure(IRunIterator iterator, IRun run) { + return ((null == iterator) || iterator.abortOnFailure()); // XxX not complete + } + + + /** + * Tell Runner to stop iterating over IRun for an IRunIterator + * if any IRun.run(IRunStatus) fails. + * This overrides a false result from IRunIterator.abortOnFailure(). + * @param iterator the IRunIterator to fast-fail - ignored if null. + * @see IRunIterator#abortOnFailure() + */ + public void registerFastFailIterator(IRunIterator iterator) { // XXX unimplemented + throw new UnsupportedOperationException("ignoring " + iterator); + } + + /** + * Tell Runner to stop iterating over IRun for an IRunIterator + * if any IRun.run(IRunStatus) fails, + * if the IRunIterator is assignable to iteratorType. + * This overrides a false result from IRunIterator.abortOnFailure(). + * @param iteratorType the IRunIterator Class to fast-fail + * - ignored if null, must be assignable to IRunIterator + * @see IRunIterator#abortOnFailure() + */ + public void registerFastFailIteratorTypes(Class iteratorType) { // XXX unimplemented + throw new UnsupportedOperationException("ignoring " + iteratorType); + } + + /** + * Register a run listener for any run assignable to type. + * @throws IllegalArgumentException if either is null + */ + public void registerListener(Class type, IRunListener listener) { // XXX unregister + if (null == type) { + throw new IllegalArgumentException("null type"); + } + if (null == listener) { + throw new IllegalArgumentException("null listener"); + } + ClassListeners.addListener(type, listener); + } + + /** + * Wrap this IRunIterator. + * This wrapper takes care of calling + * iterator.iterationCompleted() when done, so + * after running this, clients should not invoker IRunIterator + * methods on this iterator. + * @return the iterator wrapped as a single IRun + */ + public IRun wrap(IRunIterator iterator, IRunListener listener) { + LangUtil.throwIaxIfNull(iterator, "iterator"); + return new IteratorWrapper(this, iterator, listener); + } + + /** + * This gets any listeners registered for the run + * based on the class of the run (or of the wrapped iterator, + * if the run is an IteratorWrapper). + * @return a listener with all registered listener and parm, + * or null if parm is null and there are no listeners + */ + protected RunListeners getListeners(IRun run, IRunListener listener) { + if (null == run) { + throw new IllegalArgumentException("null run"); + } + Class runClass = run.getClass(); + if (runClass == IteratorWrapper.class) { + IRunIterator it = ((IteratorWrapper) run).iterator; + if (null != it) { + runClass = it.getClass(); + } + // fyi expecting: listener == ((IteratorWrapper) run).listener + } + RunListeners listeners = ClassListeners.getListeners(runClass); + if (null != listener) { + listeners.addListener(listener); + } + return listeners; // XXX implement registration + } + + /** check status and run before running */ + private boolean precheck(IRun run, IRunStatus status) { + if (null == status) { + throw new IllegalArgumentException("null status"); + } + // check abort request coming in + if (status.aborted()) { + return status.runResult(); + } else if (status.isCompleted()) { + throw new IllegalStateException("status completed before starting"); + } + + if (!status.started()) { + status.start(); + } + return true; + } + + /** This assumes precheck has happened and listeners have been obtained */ + private boolean runPrivate(IRun run, IRunStatus status, + RunListeners listeners, Class exceptionClass) { + IRunListener listener = listeners; + if (null != listener) { + try { + listener.runStarting(status); + } catch (Throwable t) { + String m = listener + " RunListenerI.runStarting(..) exception"; + IMessage mssg = new Message(m, IMessage.WARNING, t, null); + // XXX need IMessage.EXCEPTION - WARNING is ambiguous + status.handleMessage(mssg); + } + } + // listener can set abort request + if (status.aborted()) { + return status.runResult(); + } + if (null == run) { + if (!status.isCompleted()) { + status.abort(IRunStatus.ABORT_NORUN); + } else { + status.handleMessage(MessageUtil.FAIL_INCOMPLETE); + } + return false; + } + + boolean result = false; + try { + result = run.run(status); + if (!status.isCompleted()) { + status.finish(result?IRunStatus.PASS: IRunStatus.FAIL); + } + } catch (Throwable thrown) { + if (!status.isCompleted()) { + status.thrown(thrown); + } else { + String m = "run status completed but run threw exception"; + status.handleMessage(MessageUtil.abort(m, thrown)); + result = false; + } + } finally { + if (!status.isCompleted()) { + // XXX should never get here... - hides errors to set result + status.finish(result ? IRunStatus.PASS : IRunStatus.FAIL); + if (!status.isCompleted()) { + status.handleMessage(MessageUtil.debug("child set of status failed")); + } + } + } + + + try { + if ((null != listener) && !status.aborted()) { + listener.runCompleted(status); + } + } catch (Throwable t) { + String m = listener + " RunListenerI.runCompleted(..) exception"; + status.handleMessage(MessageUtil.abort(m, t)); + } + return result; + } + + //---------------------------------- nested classes + /** + * Wrap an IRunIterator as a IRun, coordinating + * fast-fail IRunIterator and Runner + */ + public static class IteratorWrapper implements IRun { + final Runner runner; + public final IRunIterator iterator; + final IRunListener listener; + + public IteratorWrapper(Runner runner, IRunIterator iterator, IRunListener listener) { + LangUtil.throwIaxIfNull(iterator, "iterator"); + LangUtil.throwIaxIfNull(runner, "runner"); + this.runner = runner; + this.iterator = iterator; + this.listener = listener; + } + + /** @return null */ + public IRunStatus makeStatus() { + return null; + } + + /** @return true unless one failed */ + public boolean run(IRunStatus status) { + boolean result = true; + try { + int i = 0; + int numMessages = status.numMessages(IMessage.FAIL, IMessageHolder.ORGREATER); + while (iterator.hasNextRun()) { + IRun run = iterator.nextRun((IMessageHandler) status, runner); + if (null == run) { + MessageUtil.debug(status, "null run " + i + " from " + iterator); + continue; + } + + int newMessages = status.numMessages(IMessage.FAIL, IMessageHolder.ORGREATER); + if (newMessages > numMessages) { + numMessages = newMessages; + String m = "run " + i + " from " + iterator + + " not invoked, due to fail(+) message(s) "; + MessageUtil.debug(status, m); + continue; + } + RunStatus childStatus = null; // let runChild create + if (!runner.runChild(run, status, childStatus, listener)) { + if (result) { + result = false; + } + if (iterator.abortOnFailure() + || runner.abortOnFailure(iterator, run)) { + break; + } + } + i++; + } + return result; + } finally { + iterator.iterationCompleted(); + } + } + + /** @return iterator, clipped to 75 char */ + public String toString() { + String s = "" + iterator; + if (s.length() > 75) { + s = s.substring(0, 72) + "..."; + } + return s; + } + } + + /** per-class collection of IRun */ + static class ClassListeners extends RunListeners { + private static final Hashtable known = new Hashtable(); + + static RunListeners getListeners(Class c) { // XXX costly and stupid + Enumeration keys = known.keys(); + RunListeners many = new RunListeners(); + while (keys.hasMoreElements()) { + Class kc = (Class) keys.nextElement(); + if (kc.isAssignableFrom(c)) { + many.addListener((IRunListener) known.get(kc)); + } + } + return many; + } + + private static RunListeners makeListeners(Class c) { + RunListeners result = (ClassListeners) known.get(c); + if (null == result) { + result = new ClassListeners(c); + known.put(c, result); + } + return result; + } + + static void addListener(Class c, IRunListener listener) { + if (null == listener) { + throw new IllegalArgumentException("null listener"); + } + if (null == c) { + c = IRun.class; + } + makeListeners(c).addListener(listener); + } + + Class clazz; + + ClassListeners(Class clazz) { + this.clazz = clazz; + } + + public String toString() { + return clazz.getName() + " ClassListeners: " + super.toString(); + } + } +} diff --git a/testing/src/org/aspectj/testing/run/WrappedRunIterator.java b/testing/src/org/aspectj/testing/run/WrappedRunIterator.java new file mode 100644 index 000000000..e8c8bc064 --- /dev/null +++ b/testing/src/org/aspectj/testing/run/WrappedRunIterator.java @@ -0,0 +1,72 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.run; + +import org.aspectj.bridge.IMessageHandler; +import org.aspectj.util.LangUtil; + +/** Adapt IRun to IRunIterator in a way that can be detected for hoisting. */ +public class WrappedRunIterator implements IRunIterator { + protected final Object id; + protected IRun run; + + /** + * @param id the Object used for toString(), if set + * @param run the IRun returned from the first call to + * nextRun(IMessageHandler handler, Runner runner) + */ + public WrappedRunIterator(Object id, IRun run) { + LangUtil.throwIaxIfNull(run, "run"); + this.id = id; + this.run = run; + } + + /** @return false always - we run only once anyway */ + public boolean abortOnFailure() { + return false; + } + + /** + * @return true until nextRun() completes + * @see org.aspectj.testing.run.RunIteratorI#hasNextRun() + */ + public boolean hasNextRun() { + return (null != run); + } + + /** + * @see org.aspectj.testing.run.IRunIterator#iterationCompleted() + */ + public void iterationCompleted() { + } + + /** + * @return the only IRun we have, and null thereafter + * @see org.aspectj.testing.run.RunIteratorI#nextRun(IMessageHandler, Runner) + */ + public IRun nextRun(IMessageHandler handler, Runner runner) { + if (null == run) { + return null; + } else { + IRun result = run; + run = null; + return result; + } + } + + /** @return name */ + public String toString() { + return (null == id ? run : id).toString(); + } +} diff --git a/testing/src/org/aspectj/testing/util/AccumulatingFileFilter.java b/testing/src/org/aspectj/testing/util/AccumulatingFileFilter.java new file mode 100644 index 000000000..5ef0ecf34 --- /dev/null +++ b/testing/src/org/aspectj/testing/util/AccumulatingFileFilter.java @@ -0,0 +1,53 @@ +/* ******************************************************************* + * Copyright (c) 1999-2000 Xerox Corporation. + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +package org.aspectj.testing.util; + +import java.io.File; +import java.util.Vector; + +/** + * A FileFilter that accumulates the results when called if they exist. + * Subclasses override accumulate to determine whether it should be + * accumulated. + */ +public class AccumulatingFileFilter extends ValidFileFilter { + Vector files = new Vector(); + public final boolean accept(File f) { + if (super.accept(f) && (accumulate(f))) { + files.add(f); + } + return true; + } + + /** + * This implementation accumulates everything. + * Subclasses should override to implement filter + * @param file a File guaranteed to exist + * @return true if file should be accumulated. + */ + public boolean accumulate(File f) { + return true; + } + /** + * @return list of files currently accumulated + */ + public File[] getFiles() { + int numFiles = files.size(); + File[] result = new File[numFiles]; + if (0 < numFiles) { + files.copyInto(result); + } + return result; + } +} diff --git a/testing/src/org/aspectj/testing/util/BridgeUtil.java b/testing/src/org/aspectj/testing/util/BridgeUtil.java new file mode 100644 index 000000000..8e4b60fca --- /dev/null +++ b/testing/src/org/aspectj/testing/util/BridgeUtil.java @@ -0,0 +1,439 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.util; + +import org.aspectj.bridge.IMessage; +import org.aspectj.bridge.ISourceLocation; +import org.aspectj.bridge.Message; +import org.aspectj.bridge.MessageUtil; +import org.aspectj.bridge.SourceLocation; +import org.aspectj.testing.run.IRunStatus; +import org.aspectj.testing.run.RunValidator; +import org.aspectj.util.FileUtil; +import org.aspectj.util.LineReader; + +import java.io.File; +import java.util.Comparator; + +/** + * + */ +public class BridgeUtil { + + private static final String INDENT = " "; + + /** result value when writeMessage is passed null */ + private static final String NULL_MESSAGE_OUTPUT = ""; + + /** result value when readMessage is passed null */ + private static final IMessage NULL_MESSAGE_INPUT = null; + + private static final String KIND_DELIM = ": \""; + private static final String MESSAGE_DELIM = "\" - "; + + + public static ISourceLocation makeSourceLocation(LineReader reader) { + LangUtil.throwIaxIfNull(reader, "reader"); + int line = reader.getLineNumber(); + + return new SourceLocation(reader.getFile(), line, line, 0); + } + + + /** + * Method readSourceLocation. + * @param sourceLocStr + * @return ISourceLocation + */ + private static ISourceLocation readSourceLocation(String sourceLocStr) { + return BridgeUtil.makeSourceLocation(sourceLocStr); + } + public static IMessage makeMessage(String message, IMessage.Kind kind, + Throwable thrown, LineReader reader) { + ISourceLocation sl = (null == reader ? null : MessageUtil.makeSourceLocation(reader)); + if (null == kind) kind = IMessage.INFO; + return new Message(message, kind, thrown, sl); + } + + /** + * Read a message from a string written by writeMessage(IMessage). + * Does not handle exceptions at all or source location well. XXX + * @param message the String representation of a message + * @return IMessage + */ + public static IMessage readMessage(String message) { + if (null == message) { + return NULL_MESSAGE_INPUT; + } + if (NULL_MESSAGE_OUTPUT.equals(message)) { + return null; + } + int kindEnd = message.indexOf(KIND_DELIM); + int messageEnd = message.indexOf(MESSAGE_DELIM); + int messageStart = kindEnd+KIND_DELIM.length(); + int sourceLocStart = messageEnd+MESSAGE_DELIM.length(); + String kindStr = message.substring(0, kindEnd); + String text = message.substring(messageStart, messageEnd); + String sourceLocStr = message.substring(sourceLocStart); + IMessage.Kind kind = MessageUtil.getKind(kindStr); + ISourceLocation loc = readSourceLocation(sourceLocStr); + return new Message(text, kind, null, loc); + } + + + /** + * Write a message to a string to be read by readMessage(String) + * @param message the String representation of a message + * @return IMessage + */ + public static String writeMessage(IMessage message) { + if (null == message) { + return NULL_MESSAGE_OUTPUT; + } + return message.getKind() + + KIND_DELIM + + message.getMessage() + + MESSAGE_DELIM + + message.getISourceLocation(); // XXX implement + } + + + public static class Comparators { + /** + * This is a weak ordering + * so use only for sorts, not to maintain maps. + * It returns 0 if one file path is a suffix of the other + * or a case-insensitive string comparison otherwise. + */ + public static final Comparator WEAK_File = new Comparator() { + public int compare(Object o1, Object o2) { + File one = (File) o1; + File two = (File) o2; + if (null == one) { + return (null == two ? 0 : 1); + } else if (null == two) { + return -1; + } else if (one == two) { + return 0; + } else { + String s1 = FileUtil.weakNormalize(one.getPath()); + String s2 = FileUtil.weakNormalize(two.getPath()); + if (s1.endsWith(s2) || s2.endsWith(s1)) { + return 0; + } else { + return String.CASE_INSENSITIVE_ORDER.compare(s1, s2); + } + } + }}; + /** + * Ordering ignores filename, + * so use only for sorts, not to maintain maps + */ + public static final Comparator WEAK_ISourceLocation = new Comparator() { + public int compare(Object o1, Object o2) { + ISourceLocation one = (ISourceLocation) o1; + ISourceLocation two = (ISourceLocation) o2; + if (null == one) { + return (null == two ? 0 : 1); + } else if (null == two) { + return -1; + } else if (one == two) { + return 0; + } else { // XXX start with File to make strong + int i1 = one.getLine(); + int i2 = two.getLine(); + return i1 - i2; +// // defer subcomparisons until messages complete +// if (i1 != i2) { +// return i1 - i2; +// } else { +// i1 = one.getColumn(); +// i2 = two.getColumn(); +// if (i1 != i2) { +// return i1 - i2; +// } else { +// i1 = one.getEndLine(); +// i2 = two.getEndLine(); +// return i1 - i2; +// } +// } + } + }}; + + /** + * Ordering ignores filename and message + * so use only for sorts, not to maintain maps + */ + public static final Comparator WEAK_IMessage = new Comparator() { + public int compare(Object o1, Object o2) { + IMessage one = (IMessage) o1; + IMessage two = (IMessage) o2; + if (null == one) { + return (null == two ? 0 : 1); + } else if (null == two) { + return -1; + } else if (one == two) { + return 0; + } else { + IMessage.Kind kind1 = one.getKind(); + IMessage.Kind kind2= two.getKind(); + if (kind1 != kind2) { + return IMessage.Kind.COMPARATOR.compare(kind1, kind2); + } else { + ISourceLocation sl1 = one.getISourceLocation(); + ISourceLocation sl2 = two.getISourceLocation(); + return WEAK_ISourceLocation.compare(sl1, sl2); + } + } + }}; + + } + + public static SourceLocation makeSourceLocation(String input) { // XXX only for testing, not production + return makeSourceLocation(input, (File) null); + } + + public static SourceLocation makeSourceLocation(String input, String path) { + return makeSourceLocation(input, (null == path ? null : new File(path))); + } + + /** attempt to create a source location from the input */ + public static SourceLocation makeSourceLocation(String input, File defaultFile) { + /* + * Forms interpreted: + * # - line + * file - file + * file:# - file, line + * #:# - if defaultFile is not null, then file, line, column + * file:#:# - file, line, column + * file:#:#:? - file, line, column, message + */ + SourceLocation result = null; + if ((null == input) || (0 == input.length())) { + if (null == defaultFile) { + return null; + } else { + return new SourceLocation(defaultFile, 0, 0, 0); + } + } + input = input.trim(); + + String path = null; + int line = 0; + int endLine = 0; + int column = 0; + String message = null; + + // first try line only + line = convert(input); + if (-1 != line) { + return new SourceLocation(defaultFile, line, line, 0); + } + + // if not a line - must be > 2 characters + if (3 > input.length()) { + return null; // throw new IllegalArgumentException("too short: " + input); + } + final String fixTag = "FIXFIX"; + if (input.charAt(1) == ':') { // windows drive ambiguates ":" file:line:col separator + input = fixTag + input.substring(0,1) + input.substring(2); + } + // expecting max: path:line:column:message + // if 1 colon, delimits line (to second colon or end of string) + // if 2 colon, delimits column (to third colon or end of string) + // if 3 colon, delimits column (to fourth colon or end of string) + // todo: use this instead?? + final int colon1 = input.indexOf(":",2); // 2 to get past windows drives... + final int colon2 = (-1 == colon1?-1:input.indexOf(":", colon1+1)); + final int colon3 = (-1 == colon2?-1:input.indexOf(":", colon2+1)); + String s; + if (-1 == colon1) { // no colon; only path (number handled above) + path = input; + } else { // 1+ colon => file:line // XXX later or line:column + path = input.substring(0, colon1); + s = input.substring(colon1+1,(-1!=colon2?colon2:input.length())).trim(); + line = convert(s); + if (-1 == line) { + return null; + //line = "expecting line(number) at \"" + line + "\" in " + input; + //throw new IllegalArgumentException(line); + } else if (-1 != colon2) { // 2+ colon => col + s = input.substring(colon2+1,(-1!=colon3?colon3:input.length())).trim(); + column = convert(s); + if (-1 == column) { + return null; + //col = "expecting col(number) at \"" + col + "\" in " + input; + //throw new IllegalArgumentException(col); + } else if (-1 != colon3) { // 3 colon => message + message = input.substring(colon3+1); // do not trim message + } + } + } + + if (path.startsWith(fixTag)) { + int len = fixTag.length(); + path = path.substring(len, 1+len) + ":" + + path.substring(1+len); + } + if ((endLine == 0) && (line != 0)) { + endLine = line; + } + // XXX removed message/comment + return new SourceLocation(new File(path), line, endLine, column); + } + + // XXX reconsider convert if used in production code + /** + * Convert String to int using ascii and optionally + * tolerating text + * @param s the String to convert + * @param permitText if true, pick a sequence of numbers + * within a possibly non-numeric String + * @param last if permitText, then if this is true the + * last sequence is used - otherwise the first is used + * XXX only default u.s. encodings.. + * @return -1 or value if a valid, totally-numeric positive string 0..MAX_WIDTH + */ + private static int convert(String s) { + return convert(s, false, false); + } + + // XXX reconsider convert if used in production code + /** + * Convert String to int using ascii and optionally + * tolerating text + * @param s the String to convert + * @param permitText if true, pick a sequence of numbers + * within a possibly non-numeric String + * @param last if permitText, then if this is true the + * last sequence is used - otherwise the first is used + * XXX only default u.s. encodings.. + * @return -1 or value if a valid, positive string 0..MAX_WIDTH + */ + private static int convert(String s, boolean permitText, + boolean first) { + int result = -1; + int last = -1; + int max = s.length(); + boolean reading = false; + for (int i = 0; i < max; i++) { + char c = s.charAt(i); + if ((c >= '0') && (c <= '9')) { + if (-1 == result) { // prefix loop + result = 0; + reading = true; + } + result = ((result * 10) + (c - '0')); + } else if (!permitText) { + return -1; + } else if (reading) { // from numeric -> non-numeric + if (first) { + return result; + } else { + last = result; + } + reading = false; + } + } + if (permitText && !first && (-1 != last) && (-1 == result)) { + result = last; + } + return ((0 < result) && (result < ISourceLocation.MAX_LINE) ? result : -1); + } + + private BridgeUtil() {} + + /** @return String for status header, counting children passed/failed */ + public static String childString(IRunStatus runStatus, int numSkips, int numIncomplete) { + if (null == runStatus) { + return "((RunStatus) null)"; + } + if (0 > numSkips) { + numSkips = 0; + } + if (0 > numIncomplete) { + numIncomplete = 0; + } + StringBuffer sb = new StringBuffer(); + if (RunValidator.NORMAL.runPassed(runStatus)) { + sb.append("PASS "); + } else { + sb.append("FAIL "); + } + Object id = runStatus.getIdentifier(); + if (null != id) { + sb.append(id.toString() + " "); + } + IRunStatus[] children = runStatus.getChildren(); + final int numChildren = (null == children ? 0 : children.length); + final int numTests = numIncomplete + numChildren + numSkips; + int numFails = 0; + if (!LangUtil.isEmpty(children)) { + for (int i = 0; i < children.length; i++) { + if (!RunValidator.NORMAL.runPassed(children[i])) { + numFails++; + } + } + } + final int numPass = children.length - numFails; + sb.append(numTests + " tests"); + if (0 < numTests) { + sb.append(" ("); + } + if (0 < numSkips) { + sb.append(numSkips + " skipped"); + if (0 < (numFails + numPass + numIncomplete)) { + sb.append(", "); + } + } + if (0 < numIncomplete) { + sb.append(numIncomplete + " incomplete"); + if (0 < (numFails + numPass)) { + sb.append(", "); + } + } + if (0 < numFails) { + sb.append(numFails + " failed"); + if (0 < numPass) { + sb.append(", "); + } + } + if (0 < numPass) { + sb.append(numPass + " passed)"); + } else if (0 < numTests) { + sb.append(")"); + } + return sb.toString().trim(); + } + + /** @return String for status header */ + public static String toShortString(IRunStatus runStatus) { + if (null == runStatus) { + return "((RunStatus) null)"; + } + StringBuffer sb = new StringBuffer(); + if (RunValidator.NORMAL.runPassed(runStatus)) { + sb.append("PASS "); + } else { + sb.append("FAIL "); + } + Object id = runStatus.getIdentifier(); + if (null != id) { + sb.append(id.toString() + " "); + } + sb.append(MessageUtil.renderCounts(runStatus)); + return sb.toString().trim(); + } + +} diff --git a/testing/src/org/aspectj/testing/util/CollectorFileFilter.java b/testing/src/org/aspectj/testing/util/CollectorFileFilter.java new file mode 100644 index 000000000..ef268bcaa --- /dev/null +++ b/testing/src/org/aspectj/testing/util/CollectorFileFilter.java @@ -0,0 +1,88 @@ +/* ******************************************************************* + * Copyright (c) 1999-2000 Xerox Corporation. + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +package org.aspectj.testing.util; + +import java.io.File; +import java.io.FileFilter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Wrap FileFilter to collect any accepted + */ +public class CollectorFileFilter implements FileFilter { + /** returned from getFiles() when there are no files to get */ + public static final List EMPTY + = Collections.unmodifiableList(new ArrayList(0)); + + /** used for collecting filters */ + protected ArrayList files; + + /** filter delegate - may be null */ + protected final FileFilter filter; + + /** return false from accept only when !alwaysTrue + * and filter is null or fails + */ + protected final boolean alwaysTrue; + + /** this(null, true) */ + public CollectorFileFilter() { + this(null, true); + } + + /* + * @param filter the FileFilter delegate - may be null + * @param alwaysTrue return false from accept only when !alwaysTrue + * and filter is null or fails + */ + public CollectorFileFilter(FileFilter filter, boolean alwaysTrue){ + this.filter = filter; + this.alwaysTrue = alwaysTrue; + } + + /** + * Accept file into collection if filter is null or passes. + * @return false only when !alwaysTrue and filter fails. + */ + public boolean accept(File f) { + if ((null == filter) || filter.accept(f)) { + add(f); + return true; + } + return alwaysTrue; + } + + /** gather files */ + protected synchronized void add(File f) { + if (null != f) { + if (null == files) { + files = new ArrayList(); + } + files.add(f); + } + } + + /** + * return clone of gathered-files + * @return EMPTY if no files or a clone of the collection otherwise + */ + public synchronized List getFiles() { + if ((null == files) || (0 == files.size())) { + return EMPTY; + } + return (List) files.clone(); + } +} diff --git a/testing/src/org/aspectj/testing/util/Diffs.java b/testing/src/org/aspectj/testing/util/Diffs.java new file mode 100644 index 000000000..1adf40924 --- /dev/null +++ b/testing/src/org/aspectj/testing/util/Diffs.java @@ -0,0 +1,103 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.util; + +import org.aspectj.bridge.IMessage; +import org.aspectj.bridge.IMessageHandler; +import org.aspectj.bridge.MessageUtil; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; + +/** result struct for expected/actual diffs for Collection */ +public class Diffs { + + // XXX List -> Collection b/c comparator orders + public static final Diffs NONE + = new Diffs("NONE", Collections.EMPTY_LIST, Collections.EMPTY_LIST); + + /** name of the thing being diffed - used only for reporting */ + public final String label; + + /** immutable List */ + public final List missing; + + /** immutable List */ + public final List unexpected; + + /** true if there are any missing or unexpected */ + public final boolean different; + + private Diffs(String label, List missing, List unexpected) { + this.label = label; + this.missing = missing; + this.unexpected = unexpected; + different = ((0 != this.missing.size()) + || (0 != this.unexpected.size())); + } + + public Diffs(String label, List expected, List actual, Comparator comparator) { + label = label.trim(); + if (null == label) { + label = ": "; + } else if (!label.endsWith(":")) { + label += ": "; + } + this.label = " " + label; + ArrayList miss = new ArrayList(); + ArrayList unexpect = new ArrayList(); + + org.aspectj.testing.util.LangUtil.makeSoftDiffs(expected, actual, miss, unexpect, comparator); + missing = Collections.unmodifiableList(miss); + unexpected = Collections.unmodifiableList(unexpect); + different = ((0 != this.missing.size()) + || (0 != this.unexpected.size())); + } + + /** + * Report missing and extra items to handler. + * For each item in missing or unexpected, this creates a {kind} IMessage with + * the text "{missing|unexpected} {label}: {message}" + * where {message} is the result of + * MessageUtil.renderMessage(IMessage). + * @param handler where the messages go - not null + * @param kind the kind of message to construct - not null + * @param label the prefix for the message text - if null, "" used + * @see MessageUtil#renderMessage(IMessage) + */ + public void report(IMessageHandler handler, IMessage.Kind kind) { + LangUtil.throwIaxIfNull(handler, "handler"); + LangUtil.throwIaxIfNull(kind, "kind"); + if (different) { + for (Iterator iter = missing.iterator(); iter.hasNext();) { + String s = MessageUtil.renderMessage((IMessage) iter.next()); + MessageUtil.fail(handler, "missing " + label + s); + } + for (Iterator iter = unexpected.iterator(); iter.hasNext();) { + String s = MessageUtil.renderMessage((IMessage) iter.next()); + MessageUtil.fail(handler, "unexpected " + label + s); + } + } + } + + /** @return "{label}: (unexpected={#}, missing={#})" */ + public String toString() { + return label + "(unexpected=" + unexpected.size() + + ", missing=" + missing.size() + ")"; + } +} + diff --git a/testing/src/org/aspectj/testing/util/FileUtil.java b/testing/src/org/aspectj/testing/util/FileUtil.java new file mode 100644 index 000000000..2caa5e203 --- /dev/null +++ b/testing/src/org/aspectj/testing/util/FileUtil.java @@ -0,0 +1,745 @@ +/* ******************************************************************* + * Copyright (c) 1999-2000 Xerox Corporation. + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +package org.aspectj.testing.util; + +import java.io.File; +import java.io.FileFilter; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.io.StringBufferInputStream; +import java.io.StringWriter; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.Vector; +import java.util.jar.Attributes; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipOutputStream; + +/** + * misc file utilities + */ +public class FileUtil { + + /** default filename if URL has none (i.e., a directory URL): index.html */ + public static final String DEFAULT_URL_FILENAME = "index.html"; + + /** + * @param args the String[] + * { "-copy", "-srcFile" | "-srcUrl", {src}, "-destFile", {destFile} } + */ + public static void main (String[] args) { + if (null == args) return; + for (int i = 0; (i+4) < args.length; i++) { + if ("-copy".equals(args[i])) { + String arg = args[++i]; + String src = null; + String destFile = null; + boolean srcIsFile = ("-srcFile".equals(arg)); + if (srcIsFile) { + src = args[++i]; + } else if ("-srcUrl".equals(arg)) { + src = args[++i]; + } + if ((null != src) && ("-destFile".equals(args[++i]))) { + destFile = args[++i]; + StringBuffer errs = new StringBuffer(); + if (srcIsFile) { + copyFile(new File(src), new File(destFile), errs); + } else { + URL url = null; + try { url = new URL(src) ; } + catch (MalformedURLException e) { render(e, errs); } + if (null != url) { + copyURL(url, new File(destFile), errs); + } + } + if (0 < errs.length()) { + System.err.println("Error copying " + src + " to " + destFile); + System.err.println(errs.toString()); + + } + } + } // ("-copy".equals(args[i])){ + } + } // end of main () + + /** + * Generate a list of missing and extra files by comparison to a + * timestamp, optionally excluding certain files. + * This is a call to select all files after a given time: + * + *

    Diffs d = dirDiffs(dir, givenTime, null, null, null);
    + * + * Given files + *
    classes/Foo.class
    +     * classes/bar/Bash.class
    +     * classes/Old.class
    +     * classes/one/Unexpected.class
    +     * classes/start.gif
    + * where only Old.class predated startTime, this is a call that + * reports "one/Unexpected.class" as unexpected and "Foo" + * as missing: + *
    String requireSuffix = ".class";
    +     * String[] expectedPaths = new String[] { "Foo", "bar/Bas" };
    +     * File file = new File("classes");
    +     * Diffs d = dirDiffs(dir, startTime, requireSuffix,expectedPaths, true);
    + * + * @param label the String to use for the Diffs label + * @param dir the File for the dir to search + * @param startTime collect files modified after this time + * (ignored if less than 0) + * @param requireSuffix ignore all actual files without this suffix + * (ignored if null) + * @param expectedPaths paths (relative to dir) of the expected files + * (if null, none expected) + * @param acceptFilePrefix if true, then accept a file which + * differs from an expected file name only by a suffix + * (which need not begin with "."). + */ + public static Diffs dirDiffs( // XXX too complicated, weak prefix checking + final String label, + final File dir, + final long startTime, + final String requiredSuffix, + final String[] expectedPaths, + final boolean acceptFilePrefix) { + + LangUtil.throwIaxIfNull(dir, "dir"); + final boolean checkExpected = !LangUtil.isEmpty(expectedPaths); + + // normalize sources to ignore + final ArrayList expected = (!checkExpected ? null : new ArrayList()); + if (checkExpected) { + for (int i = 0; i < expectedPaths.length; i++) { + String srcPath = expectedPaths[i]; + if (!LangUtil.isEmpty(srcPath)) { + expected.add(org.aspectj.util.FileUtil.weakNormalize(srcPath)); + } + } + } + + // gather, normalize paths changed + FileFilter touchedCollector = new FileFilter() { + /** + * For files complying with time and suffix rules, + * return true (accumulate - unexpected) + * unless they match expected files, + * (deleting any matches from sources + * so the remainder is missing). + * @return true for unexpected files after date */ + public boolean accept(File file) { + if (file.isFile() + && ((0 > startTime) + || (startTime < file.lastModified()))) { + String path = file.getPath(); + if ((null == requiredSuffix) || path.endsWith(requiredSuffix)) { + path = org.aspectj.util.FileUtil.weakNormalize(path); + if (checkExpected) { + if (!acceptFilePrefix) { + // File.equals(..) does lexical compare + if (expected.contains(path)) { + expected.remove(path); + // found - do not add to unexpected + return false; + } + } else { + for (Iterator iter = expected.iterator(); + iter.hasNext(); + ) { + String exp = (String) iter.next(); + if (path.startsWith(exp)) { + String suffix = path.substring(exp.length()); + if (-1 == suffix.indexOf("/")) { // normalized... + expected.remove(path); + // found - do not add to unexpected + return false; + } + } + } + } + } + // add if is file, right time, and have or don't need suffix + return true; + } + } + // skip if not file or not right time + return false; + } + }; + ArrayList unexp = new ArrayList(); + unexp.addAll(Arrays.asList(dir.listFiles(touchedCollector))); + + // report any unexpected changes + return new Diffs(label, expected, unexp, String.CASE_INSENSITIVE_ORDER); + } + + + /** + * Visit the entries in a zip file, halting when visitor balks. + * Errors are silently ignored. + * @throws IllegalArgumentException if zipfile or visitor is null + */ + public static void visitZipEntries(ZipFile zipfile, StringVisitor visitor) { + visitZipEntries(zipfile, visitor, (StringBuffer) null); + } + + /** + * Visit the entries in a zip file, halting when visitor balks. + * Errors are reported in errs, if not null. + * @throws IllegalArgumentException if zipfile or visitor is null + */ + public static void visitZipEntries(ZipFile zipfile, StringVisitor visitor, + StringBuffer errs) { + if (null == zipfile) throw new IllegalArgumentException("null zipfile"); + if (null == visitor) throw new IllegalArgumentException("null visitor"); + int index = 0; + try { + Enumeration enum = zipfile.entries(); + while (enum.hasMoreElements()) { + ZipEntry entry = (ZipEntry) enum.nextElement(); + index++; + if (! visitor.accept(entry.getName())) { + break; + } + } + } catch (Throwable e) { + if (null != errs) { + errs.append("FileUtil.visitZipEntries error accessing entry " + index + + ": " + e.getMessage()); + StringWriter sw = new StringWriter(); + e.printStackTrace(new PrintWriter(sw)); + errs.append(sw.toString()); + } + } finally { + if (null != zipfile) { + try { zipfile.close(); } + catch (IOException x) {} // ignore + } + } + } + + /** + * descend filesystem tree, invoking FileFilter.accept() on files. + * E.g., To list files from current directory: + *
    descendFileTree(new File("."), new FileFilter() {
    +	 *     public boolean accept(File f){
    +	 *        System.out.println(f.getAbsolutePath());
    +     *        return true;
    +	 *     }});
    + * @param file root/starting point. If a file, the only one visited. + * @param filter supplies accept(File) routine + */ + public static void descendFileTree(File file, FileFilter filter) { + descendFileTree(file, filter, false); + } + + /** + * Descend filesystem tree, invoking FileFilter.accept() on files + * and, if userRecursion, on dirs. If userRecursion, accept() must + * call descendFileTree() again to recurse down directories. + * This calls fileFilter.accept(File) on all files before doing any dirs. + * E.g., To list only files from Unix root: + *
    descendFileTree(new File("/"), new FileFilter() {
    +     *     public boolean run(File f){
    +     *        System.out.println(f.getAbsolutePath());
    +     *        return true;
    +     *     }}, false);
    + * To list files/dir from root using user recursion: + *
    descendFileTree(new File("/"), new FileFilter() {
    +     *     public boolean run(File f){ 
    +     *        System.out.println(f.getAbsolutePath());
    +     *        if (f.isDirectory() && (-1 == f.getName().indexOf("CVS")))
    +     *           return descendFileTree(f, this, true);
    +     *        return true;
    +     *     }}, true);
    + * @param file root/starting point. If a file, the only one visited. + * @param filter supplies boolean accept(File) method + * @param userRecursion - if true, do accept() on dirs; else, recurse + * @return false if any fileFilter.accept(File) did. + * @throws IllegalArgumentException if file or fileFilter is null + */ + public static boolean descendFileTree(File file, FileFilter fileFilter, + boolean userRecursion) { + if (null == file) {throw new IllegalArgumentException("parm File"); } + if (null == fileFilter){throw new IllegalArgumentException("parm FileFilter");} + + if (!file.isDirectory()) { + return fileFilter.accept(file); + } else if (file.canRead()) { + // go through files first + File[] files = file.listFiles(ValidFileFilter.FILE_EXISTS); + if (null != files) { + for (int i = 0; i < files.length; i++) { + if (!fileFilter.accept(files[i])) { + return false; + } + } + } + // now recurse to handle directories + File[] dirs = file.listFiles(ValidFileFilter.DIR_EXISTS); + if (null != dirs) { + for (int i = 0; i < dirs.length; i++) { + if (userRecursion) { + if (!fileFilter.accept(dirs[i])) { + return false; + } + } else { + if (!descendFileTree(dirs[i], fileFilter,userRecursion)) { + return false; + } + } + } + } + } // readable directory (ignore unreadable ones) + return true; + } // descendFiles + + /** + * Return the names of all files below a directory. + * If file is a directory, then all files under the directory + * are returned. If file is absolute or relative, all the files are. + * If file is a zip or jar file, then all entries in the zip or jar + * are listed. Entries inside those jarfiles/zipfiles are not listed. + * There are no guarantees about ordering. + * @param dir the File to list for + * @param results the Collection to use for the results (may be null) + * @throws IllegalArgumentException if null == dir + * @return a Collection of String of paths, including paths inside jars + */ + public static Collection directoryToString(File dir, Collection results) { + if (null == dir) throw new IllegalArgumentException("null dir"); + final Collection result = (results != null? results : new Vector()); + if (isZipFile(dir)) { + zipFileToString(dir, result); + } else if (!dir.isDirectory()) { + throw new IllegalArgumentException("not a dir: " + dir); + } else { + AccumulatingFileFilter acFilter = new AccumulatingFileFilter() { + public boolean accumulate(File file) { + String name = file.getPath(); + result.add(name); + if (isZipFile(file)) { + zipFileToString(file, result); + } + return true; + } + }; + descendFileTree(dir, acFilter, false); + } + return result; + } // directoryToString + + /** + * Render as String the entries in a zip or jar file, + * converting each to String beforehand (as jarpath!jarentry) + * applying policies for whitespace, etc. + * @param file the File to enumerate ZipEntry for + * @param results the Colection to use to return the FileLine - may be null + * @return FileLines with string as text and + * canonical as string modified by any canonicalizing policies. + */ + public static Collection zipFileToString(final File zipfile, Collection results) { + Collection result = (results != null ? results : new Vector()); + ZipFile zip = null; + try { + zip = new ZipFile(zipfile); // ZipFile.OPEN_READ| ZipFile.OPEN_DELETE); delete is 1.3 only + Enumeration enum = zip.entries(); + while (enum.hasMoreElements()) { + results.add(renderZipEntry(zipfile, (ZipEntry) enum.nextElement())); + } + zip.close(); + zip = null; + } catch (Throwable t) { + String err = "Error opening " + zipfile + " attempting to continue..."; + System.err.println(err); + t.printStackTrace(System.err); + } finally { + if (null != zip) { + try { zip.close(); } + catch (IOException e) { + e.printStackTrace(System.err); + } + } + } + return result; + } + + /** + * @return true if file represents an existing file with a zip extension + */ + public static boolean isZipFile(File f) { + String s = null; + if ((null == f) || (null == (s = f.getPath()))) { + return false; + } else { + return (f.canRead() && !f.isDirectory() + && (s.endsWith(".zip") + || (s.endsWith(".jar")))); + } + } + + /** + * Render a zip/entry combination to String + */ + public static String renderZipEntry(File zipfile, ZipEntry entry) { + String filename = (null == zipfile ? "null File" : zipfile.getName()); + String entryname = (null == entry ? "null ZipEntry" : entry.getName()); + return filename + "!" + entryname; + } + + /** + * Write all files in directory out to jarFile + * @param jarFile the File to create and write to + * @param directory the File representing the directory to read + * @param mainClass the value of the main class attribute - may be null + */ + public static boolean createJarFile(File jarFile, File directory, + String mainClass, FileFilter filter) { + String label = "createJarFile("+jarFile + +","+directory +","+mainClass +","+filter + "): "; + Log.signal(label + " start"); + if (null == directory) + throw new IllegalArgumentException("null directory"); + Manifest manifest = createManifest(mainClass); + Log.signal(label + " manifest=" + manifest); + JarOutputStream out = null; + try { + File jarFileDir = jarFile.getParentFile(); + if (null == jarFileDir) { + Log.signal(label + " null jarFileDir"); + } else if (!jarFileDir.exists() && !jarFileDir.mkdirs()) { // XXX convert to Error + Log.signal(label + " unable to create jarFileDir: " + jarFileDir); + } + OutputStream os = new FileOutputStream(jarFile); + out = (null == manifest ? new JarOutputStream(os) + : new JarOutputStream(os, manifest)); + Log.signal(label + " out=" + out); + ZipAccumulator reader = new ZipAccumulator(directory, out, filter); + Log.signal(label + " reader=" + reader); + FileUtil.descendFileTree(directory, reader); + out.closeEntry(); + return true; + } catch (IOException e) { + e.printStackTrace(System.err); // todo + } finally { + if (null != out) { + try { out.close();} + catch (IOException e) {} // todo ignored + } + } + + return false; + } + + protected static Manifest createManifest(String mainClass) { + final String mainKey = "Main-Class"; + Manifest result = null; + if (null != mainClass) { + String entry = "Manifest-Version: 1.0\n" + + mainKey + ": " + mainClass + "\n"; + try { + result = new Manifest(new StringBufferInputStream(entry)); + Attributes attributes = result.getMainAttributes(); + String main = attributes.getValue(mainKey); + if (null == main) { + attributes.putValue(mainKey, mainClass); + main = attributes.getValue(mainKey); + if (null == main) { + Log.signal("createManifest unable to set main " + + mainClass); + } + } + } catch (IOException e) { // todo ignoring + Log.signal(e, " IOException creating manifest with " + mainClass); + } + } + return result; + } + + + /** read a file out to the zip stream */ + protected static void addFileToZip(File in, File parent, + ZipOutputStream out) + throws IOException { + String path = in.getCanonicalPath(); + String parentPath = parent.getCanonicalPath(); + if (!path.startsWith(parentPath)) { + throw new Error("not parent: " + parentPath + " of " + path); + } else { + path = path.substring(1+parentPath.length()); + path = path.replace('\\', '/'); // todo: use filesep + } + ZipEntry entry = new ZipEntry(path); + entry.setTime(in.lastModified()); + // todo: default behavior is DEFLATED + + out.putNextEntry(entry); + + InputStream input = null; + try { + input = new FileInputStream(in); + byte[] buf = new byte[1024]; + int count; + while (0 < (count = input.read(buf, 0, buf.length))) { + out.write(buf, 0, count); + } + } finally { + if (null != input) input.close(); + } + } + + + public static void returnTempDir(File dir) { + deleteDirectory(dir); + } + + /** @return true if path ends with gif, properties, jpg */ + public static boolean isResourcePath(String path) { + if (null == path) return false; + path = path.toLowerCase(); + return (path.endsWith(".gif") + || path.endsWith(".properties") + || path.endsWith(".jpg") + || path.endsWith(".jpeg") + || path.endsWith(".props") + ); + } + + public static void render(Throwable t, StringBuffer err) { // todo: move + String name = t.getClass().getName(); + int loc = name.lastIndexOf("."); + name = name.substring(1+loc); + err.append(name + ": " + t.getMessage() + "\n"); // todo + StringWriter sw = new StringWriter(); + t.printStackTrace(new PrintWriter(sw)); + err.append(sw.toString()); + } + + private static boolean report(StringBuffer err, String context, String status, + Throwable throwable) { + boolean failed = ((null != status) || (null != throwable)); + if ((null != err) && (failed)) { + if (null != context) { + err.append(context); + } + if (null != status) { + err.append(status); + } + if (null != throwable) { + render(throwable, err); + } + } + return failed; + } + + /** + * Copy file. + * @param src the File to copy - must exist + * @param dest the File for the target file or directory (will not create directories) + * @param err the StringBuffer for returning any errors - may be null + **/ + public static boolean copyFile(File src, File dest, StringBuffer err) { + boolean result = false; + String label = "start"; + Throwable throwable = null; + try { + if (!ValidFileFilter.FILE_EXISTS.accept(src)) { + label = "src file does not exist"; + } else { + if (dest.isDirectory()) { + dest = new File(dest, src.getName()); + } + if (ValidFileFilter.FILE_EXISTS.accept(dest)) { + label = "dest file exists"; + } + boolean closeWhenDone = true; + result = copy(new FileInputStream(src), + new FileOutputStream(dest), + closeWhenDone); + } + label = null; + } catch (Throwable t) { + throwable = t; + } + String context = "FileUtil.copyFile(src, dest, err)"; + boolean report = report(err, context, label, throwable); + return (result && report); + } + + /** + * Copy URL to file. + * @param src the URL to copy - must exist + * @param dest the File for the target file or directory (will not create directories) + * @param err the StringBuffer for returning any errors - may be null + **/ + public static boolean copyURL(URL url, File dest, StringBuffer err) { // todo untested. + boolean result = false; + String label = "start"; + Throwable throwable = null; + try { + if (dest.isDirectory()) { + String filename = url.getFile(); + if ((null == filename) || (0 == filename.length())) { + filename = DEFAULT_URL_FILENAME; + } + dest = new File(dest, filename); + } + if (ValidFileFilter.FILE_EXISTS.accept(dest)) { + label = "dest file exists"; + } + boolean closeWhenDone = true; + result = copy(url.openConnection().getInputStream(), + new FileOutputStream(dest), + closeWhenDone); + label = null; + } catch (Throwable t) { + throwable = t; + } + String context = "FileUtil.copyURL(src, dest, err)"; // add actual parm to labels? + boolean report = report(err, context, label, throwable); + return (result && report); + } + + /** + * Copy input to output - does not close either + * @param src the InputStream to copy - must exist + * @param dest the OutputStream for the target + * @param close if true, close when done + */ + public static boolean copy(InputStream src, OutputStream dest, + boolean close) + throws IOException { + boolean result = false; + IOException throwable = null; + try { + byte[] buf = new byte[8*1024]; + int count; + while (0 < (count = src.read(buf, 0, buf.length))) { + dest.write(buf, 0, count); + } + result = true; + } catch (IOException t) { + throwable = t; + } finally { + if (close) { + try { if (null != src) src.close(); } + catch (IOException e) { + if (null == throwable) { throwable = e; } + } + try { if (null != dest) dest.close(); } + catch (IOException i) { + if (null == throwable) { throwable = i; } + } + } + } + if (null != throwable) throw throwable; + return result; + } + + /** + * @return true if dir was an existing directory that is now deleted + */ + protected static boolean deleteDirectory(File dir) { + return ((null != dir) + && dir.exists() + && dir.isDirectory() + && FileUtil.descendFileTree(dir, DELETE_FILES, false) + && FileUtil.descendFileTree(dir, DELETE_DIRS, true) + && dir.delete()); + } + + public static String[] getPaths(File[] files) { // util + String[] result = new String[files.length]; + for (int i = 0; i < result.length; i++) { + result[i] = files[i].getPath(); // preserves absolute? + } + return result; + } + + //-------- first-order, input and visible interface + + protected static final FileFilter DELETE_DIRS = new FileFilter() { + public boolean accept(File file) { + return ((null != file) && file.isDirectory() + && file.exists() && file.delete()); + } + }; + protected static final FileFilter DELETE_FILES = new FileFilter() { + public boolean accept(File file) { + return ((null != file) && !file.isDirectory() + && file.exists() && file.delete()); + } + }; + +} // class FileUtil + +/** + * Localize FileUtil log/signals for now + * ordinary signals are ignored, + * but exceptions are printed to err + * and errors are thrown as Error + */ +class Log { + /** ordinary logging - may be suppressed */ + public static final void signal(String s) { + //System.err.println(s); + } + /** print stack trace to System.err */ + public static final void signal(Throwable t, String s) { + System.err.println(s); + t.printStackTrace(System.err); + } + /** @throws Error(s) always */ + public static final void error(String s) { + throw new Error(s); + } +} + +/** read each file out to the zip file */ +class ZipAccumulator implements FileFilter { + final File parentDir; + final ZipOutputStream out; + final FileFilter filter; + public ZipAccumulator(File parentDir, ZipOutputStream out, + FileFilter filter) { + this.parentDir = parentDir; + this.out = out; + this.filter = filter; + } + public boolean accept(File f) { + if ((null != filter) && (!filter.accept(f))) { + return false; + } + try { + FileUtil.addFileToZip(f, parentDir, out); + return true; + } catch (IOException e) { + e.printStackTrace(System.err); // todo + } + return false; + } +} + diff --git a/testing/src/org/aspectj/testing/util/IntRange.java b/testing/src/org/aspectj/testing/util/IntRange.java new file mode 100644 index 000000000..80d547e47 --- /dev/null +++ b/testing/src/org/aspectj/testing/util/IntRange.java @@ -0,0 +1,118 @@ +/* ******************************************************************* + * Copyright (c) 1999-2000 Xerox Corporation. + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +package org.aspectj.testing.util; + + +import java.io.Serializable; + +/** + * imutable class to enforce an integer range + */ +public class IntRange implements IntValidator, ObjectChecker, Serializable { + /** no values permitted */ + public static final IntRange NONE = new IntRange(0, 0); + /** 0 permitted */ + public static final IntRange ZERO = new IntRange(0, 1); + /** 1 permitted */ + public static final IntRange ONE = new IntRange(1, 2); + /** 0..1 permitted */ + public static final IntRange OPTIONAL = new IntRange(0, 2); + /** 1..1000 permitted */ + public static final IntRange MANY = new IntRange(1, 1001); + + /** all positive numbers permitted except Integer.MAX_VALUE */ + public static final IntRange POSITIVE = new IntRange(1, Integer.MAX_VALUE); + /** all negative numbers permitted */ + public static final IntRange NEGATIVE = new IntRange(Integer.MIN_VALUE, 0); + /** any int number permitted except Integer.MAX_VALUE */ + public static final IntRange ANY = new IntRange(Integer.MIN_VALUE, Integer.MAX_VALUE); + + /** + * Make an IntRange that accepts this value + * (using existing if available). + * @throws IllegalArgumentException if value is Integer.MAX_VALUE. + */ + public static final IntRange make(int value) { + switch (value) { + case (1) : return ONE; + case (0) : return ZERO; + case (Integer.MAX_VALUE) : + throw new IllegalArgumentException("illegal " + value); + default : + return new IntRange(value, value + 1); + } + } + + public final int min; + public final int max; + private transient String cache; + + /** use only for serialization + * @deprecated IntRange(int, int) + */ + protected IntRange() { + min = 0; + max = 0; + } + + /** + * @param min minimum permitted value, inclusive + * @param max maximum permitted value, exclusive + */ + public IntRange(int min, int max) { + this.min = min; + this.max = max; + if (min > max) { + throw new IllegalArgumentException( min + " > " + max); + } + toString(); // create cache to view during debugging + } + + /** @return true if integer instanceof Integer with acceptable intValue */ + public final boolean isValid(Object integer) { + return ((integer instanceof Integer) + && (acceptInt(((Integer) integer).intValue()))); + } + + /** @return true if min <= value < max */ + public final boolean acceptInt(int value) { + return ((value >= min) && (value < max)); + } + + + /** + * @deprecated acceptInt(int) + * @return true if min <= value < max + */ + public final boolean inRange(int value) { + return acceptInt(value); + } + /** + * @return true if, for any int x s.t. other.inRange(x) + * is true, this.inRange(x) is also true + */ + public final boolean inRange(IntRange other) { + return ((null != other) && (other.min >= min) + && (other.max <= max)); + } + + // XXX equals(Object) + + public String toString() { + if (null == cache) { + cache = "IntRange [" + min + ".." + max + "]"; + } + return cache; + } +} diff --git a/testing/src/org/aspectj/testing/util/IntValidator.java b/testing/src/org/aspectj/testing/util/IntValidator.java new file mode 100644 index 000000000..ea3074d0e --- /dev/null +++ b/testing/src/org/aspectj/testing/util/IntValidator.java @@ -0,0 +1,21 @@ +/* ******************************************************************* + * Copyright (c) 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.util; + +/** + * @author isberg + */ +public interface IntValidator { + /** @return true if this is a valid value */ + public boolean acceptInt(int value); +} diff --git a/testing/src/org/aspectj/testing/util/IteratorWrapper.java b/testing/src/org/aspectj/testing/util/IteratorWrapper.java new file mode 100644 index 000000000..266acb13b --- /dev/null +++ b/testing/src/org/aspectj/testing/util/IteratorWrapper.java @@ -0,0 +1,147 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.util; + +import java.util.Iterator; +import java.util.List; + +/** + * This iterates in order through the permutations of Lists. + * Order and numericity depend on the underlying list iterators + * and the order in which the lists are supplied to the constructor. + * @author isberg + */ +public class IteratorWrapper implements Iterator { + + final List[] lists; + final Iterator[] iterators; + final Object[] current; + Object[] next; + + /** number of elements in each array */ + final int maxDepth; + + /** + * Current level being iterated. + * Set to 0 whenever depth is incremented. + * Incremented when iterator for the level has no more elements. + */ + int currentLevel; + + /** + * Maximum depth iterated-to thus far. + * Set to 0 on initialization. + * Incremented when incrementing currentLevel brings it past depth. + * Run completes when depth = maxDepth or any new iterator has no elements + */ + int depth; + + + /** @throws IllegalArgumentException if lists or any element null */ + public IteratorWrapper(List[] lists) { + if (null == lists) { + throw new IllegalArgumentException("null lists"); + } + maxDepth = lists.length; + currentLevel = 0; + depth = 0; + List[] temp = new List[maxDepth]; + System.arraycopy(lists, 0, temp, 0, temp.length); + for (int i = 0; i < maxDepth; i++) { + if (null == temp[i]) { + throw new IllegalArgumentException("null List[" + i + "]"); + } + } + this.lists = temp; + current = new Object[maxDepth]; + iterators = new Iterator[maxDepth]; + reset(); + } + + /** Reset to the initial state of the iterator */ + public void reset() { + next = null; + for (int i = 0; i < lists.length; i++) { + iterators[i] = lists[i].iterator(); + if (!iterators[i].hasNext()) { // one iterator is empty - never go + depth = maxDepth; + break; + } else { + current[i] = iterators[i].next(); + } + } + if (depth < maxDepth) { + next = getCurrent(); + } + } + + /** @throws UnsupportedOperationException always */ + public void remove() { + throw new UnsupportedOperationException("operation ambiguous"); + } + + public boolean hasNext() { + return (null != next); + } + + /** + * @return Object[] with each element from the iterator of the + * corresponding list which was passed to the constructor for this. + */ + public Object next() { + Object result = next; + next = getNext(); + return result; + } + + private Object[] getCurrent() { + Object[] result = new Object[maxDepth]; + System.arraycopy(current, 0, result, 0, maxDepth); + return result; + } + + private Object[] getNext() { + int initialLevel = currentLevel; + while (depth < maxDepth) { + if (iterators[currentLevel].hasNext()) { + current[currentLevel] = iterators[currentLevel].next(); + if (currentLevel > initialLevel) { + currentLevel = 0; + } + return getCurrent(); + } else { // pop + // reset this level + iterators[currentLevel] = lists[currentLevel].iterator(); + if (!iterators[currentLevel].hasNext()) { // empty iterator - quit + depth = maxDepth; + return null; + } + current[currentLevel] = iterators[currentLevel].next(); + + // do the next level + currentLevel++; + if (currentLevel > depth) { + depth++; + } + } + } + return null; + } + /** @return "IteratorWrapper({{field}={value}}..)" for current, ceiling, and max */ + public String toString() { + return "IteratorWrapper(currentLevel=" + currentLevel + + ", maxLevel=" + depth + + ", size=" + maxDepth + ")"; + } +} diff --git a/testing/src/org/aspectj/testing/util/LangUtil.java b/testing/src/org/aspectj/testing/util/LangUtil.java new file mode 100644 index 000000000..472d35a31 --- /dev/null +++ b/testing/src/org/aspectj/testing/util/LangUtil.java @@ -0,0 +1,1192 @@ +/* ******************************************************************* + * Copyright (c) 1999-2000 Xerox Corporation. + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +package org.aspectj.testing.util; + +import org.aspectj.bridge.AbortException; +import org.aspectj.bridge.IMessage; + +import java.io.File; +import java.io.FileFilter; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Modifier; +import java.security.AccessController; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Properties; +import java.util.StringTokenizer; + + +/** + * misc lang utilities + */ +public class LangUtil { + + /** Delimiter used by split(String) (and ArrayList.toString()?) */ + public static final String SPLIT_DELIM = ", "; + + /** prefix used by split(String) (and ArrayList.toString()?) */ + public static final String SPLIT_START = "["; + + /** suffix used by split(String) (and ArrayList.toString()?) */ + public static final String SPLIT_END = "]"; + + /** system-dependent classpath separator */ + public static final String CLASSPATH_SEP; + + private static final String[] NONE = new String[0]; + + /** bad: hard-wired unix, windows, mac path separators */ + private static final char[] SEPS = new char[] { '/', '\\', ':' }; + + static { + // XXX this has to be the wrong way to get system-dependent classpath separator + String ps = ";"; + try { + ps = System.getProperty("path.separator"); + if (null == ps) { + ps = ";"; + String cp = System.getProperty("java.class.path"); + if (null != cp) { + if (-1 != cp.indexOf(";")) { + ps = ";"; + } else if (-1 != cp.indexOf(":")) { + ps = ":"; + } + // else warn? + } + } + } catch (Throwable t) { // ignore + } finally { + CLASSPATH_SEP = ps; + } + } + + /** + * @return input if any are empty or no target in input, + * or input with escape prefixing all original target + */ + public static String escape(String input, String target, String escape) { + if (isEmpty(input) || isEmpty(target) || isEmpty(escape)) { + return input; + } + StringBuffer sink = new StringBuffer(); + escape(input, target, escape, sink); + return sink.toString(); + } + + + /** + * Append escaped input to sink. + * Cheap form of arbitrary escaping does not escape the escape String + * itself, but unflatten treats it as significant only before the target. + * (so this fails with input that ends with target). + */ + public static void escape(String input, String target, String escape, StringBuffer sink) { + if ((null == sink) || isEmpty(input) || isEmpty(target) || isEmpty(escape)) { + return; + } else if (-1 == input.indexOf(target)) { // avoid StringTokenizer construction + sink.append(input); + return; + } + throw new Error("unimplemented"); + } + + /** flatten list per spec to sink */ + public static void flatten(List list, FlattenSpec spec, StringBuffer sink) { + throwIaxIfNull(spec, "spec"); + final FlattenSpec s = spec; + flatten(list, s.prefix, s.nullFlattened, s.escape, s.delim, s.suffix, sink); + } + + /** + * Flatten a List to String by first converting to String[] + * (using toString() if the elements are not already String) + * and calling flatten(String[]...). + */ + public static void flatten( + List list, + String prefix, + String nullFlattened, + String escape, + String delim, + String suffix, + StringBuffer sink) { + throwIaxIfNull(list, "list"); + Object[] ra = list.toArray(); + String[] result; + if (String.class == ra.getClass().getComponentType()) { + result = (String[]) ra; + } else { + result = new String[ra.length]; + for (int i = 0; i < result.length; i++) { + if (null != ra[i]) { + result[i] = ra[i].toString(); + } + } + } + flatten(result, prefix, nullFlattened, escape, delim, suffix, sink); + } + + + + + /** flatten String[] per spec to sink */ + public static void flatten(String[] input, FlattenSpec spec, StringBuffer sink) { + throwIaxIfNull(spec, "spec"); + final FlattenSpec s = spec; + flatten(input, s.prefix, s.nullFlattened, s.escape, s.delim,s.suffix, sink); + } + + + + + /** + * Flatten a String[] to String by writing strings to sink, + * prefixing with leader (if not null), + * using nullRendering for null entries (skipped if null), + * escaping any delim in entry by prefixing with escape (if not null), + * separating entries with delim (if not null), + * and suffixing with trailer (if not null). + * Note that nullFlattened is not processed for internal delim, + * and strings is not copied before processing. + * @param strings the String[] input - not null + * @param prefix the output starts with this if not null + * @param nullFlattened the output of a null entry - entry is skipped (no delim) if null + * @param escape any delim in an item will be prefixed by escape if not null + * @param delim two items in the output will be separated by delim if not null + * @param suffix the output ends with this if not null + * @param sink the StringBuffer to use for output + * @return null if sink is not null (results added to sink) or rendering otherwise + */ + public static void flatten( + String[] strings, + String prefix, + String nullFlattened, + String escape, + String delim, + String suffix, + StringBuffer sink) { + throwIaxIfNull(strings, "strings"); + if (null == sink) { + return; + } + final boolean haveDelim = (!isEmpty(delim)); + final boolean haveNullFlattened = (null != nullFlattened); + final boolean escaping = (haveDelim && (null != escape)); + final int numStrings = (null == strings ? 0 : strings.length); + if (null != prefix) { + sink.append(prefix); + } + for (int i = 0; i < numStrings; i++) { + String s = strings[i]; + if (null == s) { + if (!haveNullFlattened) { + continue; + } + if (haveDelim && (i > 0)) { + sink.append(delim); + } + sink.append(nullFlattened); + } else { + if (haveDelim && (i > 0)) { + sink.append(delim); + } + + if (escaping) { + escape(s, delim, escape, sink); + } else { + sink.append(s); + } + } + } + if (null != suffix) { + sink.append(suffix); + } + } + + /** @return ((null == ra) || (0 == ra.length)) */ + public static boolean isEmpty(Object[] ra) { + return ((null == ra) || (0 == ra.length)); + } + + /** @return ((null == s) || (0 == s.length())); */ + public static boolean isEmpty(String s) { + return ((null == s) || (0 == s.length())); + } + + /** + * Shorthand for "if false, throw IllegalArgumentException" + * @throws IllegalArgumentException "{message}" if test is false + */ + public static final void throwIaxIfFalse(final boolean test, final String message) { + if (!test) { + throw new IllegalArgumentException(message); + } + } + + /** + * Shorthand for "if null, throw IllegalArgumentException" + * @throws IllegalArgumentException "null {name}" if o is null + */ + public static final void throwIaxIfNull(final Object o, final String name) { + if (null == o) { + String message = "null " + (null == name ? "input" : name); + throw new IllegalArgumentException(message); + } + } + + public static ArrayList unflatten(String input, FlattenSpec spec) { + throwIaxIfNull(spec, "spec"); + final FlattenSpec s = spec; + return unflatten(input,s.prefix, s.nullFlattened, s.escape, s.delim, s.suffix, s.emptyUnflattened); + } + + /** + * Unflatten a String to String[] by separating elements at delim, + * handling prefixes, suffixes, escapes, etc. + * Any prefix or suffix is stripped from the input + * (or, if not found, an IllegalArgumentException is thrown). + * If delim is null or empty or input contains no delim, + * then return new String[] {stripped input}. + * + * XXX fix comments + * prefixing with leader (if not null), + * using nullRendering for null entries (skipped if null), + * escaping any delim in entry by prefixing with escape (if not null), + * separating entries with delim (if not null), + * and suffixing with trailer (if not null). + * Note that nullRendering is not processed for internal delim, + * and strings is not copied before processing. + * @param strings the String[] input - not null + * @param prefix the output starts with this if not null + * @param nullRendering the output of a null entry - entry is skipped (no delim) if null + * @param escape any delim in an item will be prefixed by escape if not null + * @param delim two items in the output will be separated by delim if not null + * @param suffix the output ends with this if not null + * @param sink the StringBuffer to use for output + * @return null if sink is not null (results added to sink) or rendering otherwise + * @throws IllegalArgumentException if input is null + * or if any prefix does not start the input + * or if any suffix does not end the input + */ + public static ArrayList unflatten( + String input, + String prefix, + String nullFlattened, + String escape, + String delim, + String suffix, + String emptyUnflattened) { + throwIaxIfNull(input, "input"); + final boolean haveDelim = (!isEmpty(delim)); + final boolean haveNullFlattened = (null != nullFlattened); + final boolean escaping = (haveDelim && (null != escape)); + if (!isEmpty(prefix)) { + if (input.startsWith(prefix)) { + input = input.substring(prefix.length()); + } else { + String s = "expecting \"" + prefix + "\" at start of " + input + "\""; + throw new IllegalArgumentException(s); + } + } + if (!isEmpty(suffix)) { + if (input.endsWith(suffix)) { + input = input.substring(0, input.length() - suffix.length()); + } else { + String s = "expecting \"" + suffix + "\" at end of " + input + "\""; + throw new IllegalArgumentException(s); + } + } + + final ArrayList result = new ArrayList(); + if (isEmpty(input)) { + return result; + } + if ((!haveDelim) || (-1 == input.indexOf(delim))) { + result.add(input); + return result; + } + + StringTokenizer st = new StringTokenizer(input, delim, true); + StringBuffer cur = new StringBuffer(); + boolean lastEndedWithEscape = false; + boolean lastWasDelim = false; + while (st.hasMoreTokens()) { + String token = st.nextToken(); + System.out.println("reading " + token); + if (delim.equals(token)) { + } else { + result.add(token); + } + } + return result; + } + + /** combine two string arrays, removing null and duplicates + * @return concatenation of both arrays, less null in either or dups in two + * @see Util#combine(Object[], Object[]) + */ + public static String[] combine(String[] one, String[] two) { + ArrayList twoList = new ArrayList(); + twoList.addAll(org.aspectj.util.LangUtil.arrayAsList(two)); + ArrayList result = new ArrayList(); + if (null != one) { + for (int i = 0; i < one.length; i++) { + if (null != one[i]) { + twoList.remove(one[i]); + result.add(one[i]); + } + } + } + for (Iterator iterator = twoList.iterator(); iterator.hasNext(); ) { + String element = (String) iterator.next(); + if (null != element) { + result.add(element); + } + } + return (String[]) result.toArray(NONE); + } + + public static Properties combine(Properties dest, Properties add, boolean respectExisting) { // XXX + if (null == add) return dest; + if (null == dest) return add; + for (Iterator iterator = add.keySet().iterator(); iterator.hasNext(); ) { + String key = (String) iterator.next(); + if (null == key) { + continue; + } + String value = add.getProperty(key); + if (null == value) { + continue; + } + if (! respectExisting || (null == dest.getProperty(key))) { + dest.setProperty(key, value); + } + } + return dest; + } + + public static List arrayAsList(Object[] ra) { + return org.aspectj.util.LangUtil.arrayAsList(ra); + } + + /** + * return the fully-qualified class names + * inferred from the file names in dir + * assuming dir is the root of the source tree + * and class files end with ".class". + * @throws Error if dir is not properly named as prefix + * of class files found in dir. + */ + public static String[] classesIn(File dir) { + boolean alwaysTrue = true; + FileFilter filter = ValidFileFilter.CLASS_FILE; + CollectorFileFilter collector = new CollectorFileFilter(filter, alwaysTrue); + FileUtil.descendFileTree(dir, collector); + List list = collector.getFiles(); + String[] result = new String[list.size()]; + Iterator it = list.iterator(); + String dirPrefix = dir.getPath(); + for (int i = 0; i < result.length; i++) { + if (!it.hasNext()) { + throw new Error("unexpected end of list at " + i); + } + result[i] = fileToClassname((File) it.next(), dirPrefix); + } + return result; + } + + /** + * Convert String[] to String by using conventions for + * split. Will ignore any entries containing SPLIT_DELIM + * (and write as such to errs if not null). + * @param input the String[] to convert + * @param errs the StringBuffer for error messages (if any) + */ + public static String unsplit(String[] input, StringBuffer errs) { + StringBuffer sb = new StringBuffer(); + sb.append(SPLIT_START); + for (int i = 0; i < input.length; i++) { + if (-1 != input[i].indexOf(SPLIT_DELIM)) { + if (null != errs) { + errs.append("\nLangUtil.unsplit(..) - item " + i + ": \"" + input[i] + + " contains \"" + SPLIT_DELIM + "\""); + } + } else { + sb.append(input[i]); + if (1+i < input.length) { + sb.append(SPLIT_DELIM); + } + } + } + sb.append(SPLIT_END); + return sb.toString(); + + } + + /** + * Split input into substrings on the assumption that it is + * either only one string or it was generated using List.toString(), + * with tokens + *
    SPLIT_START {string} { SPLIT_DELIM {string}} SPLIT_END
    +     * (e.g., "[one, two, three]").
    +     */
    +    public static String[] split(String s) {
    +        if (null == s) {
    +            return null;
    +        }
    +        if ((!s.startsWith(SPLIT_START)) || (!s.endsWith(SPLIT_END))) {
    +           return new String[] { s };
    +        }
    +        s = s.substring(SPLIT_START.length(),s.length()-SPLIT_END.length());
    +        final int LEN = s.length();
    +        int start = 0;
    +        final ArrayList result = new ArrayList();
    +        final String DELIM = ", ";
    +        int loc = s.indexOf(SPLIT_DELIM, start);
    +        while ((start < LEN) && (-1 != loc)) {
    +            result.add(s.substring(start, loc));
    +            start = DELIM.length() + loc;
    +            loc = s.indexOf(SPLIT_DELIM, start);
    +        }
    +        result.add(s.substring(start));
    +        return (String[]) result.toArray(new String[0]);
    +    }
    + 
    +    public static String[] strip(String[] src, String[] toStrip) {
    +        if (null == toStrip) {
    +            return strip(src, NONE);
    +        } else if (null == src) {
    +            return strip(NONE, toStrip);
    +        }
    +        List slist = org.aspectj.util.LangUtil.arrayAsList(src);
    +        List tlist = org.aspectj.util.LangUtil.arrayAsList(toStrip);
    +        slist.removeAll(tlist);
    +        return (String[]) slist.toArray(NONE);        
    +    }
    +    
    +    /**
    +     * Load all classes specified by args, logging success to out
    +     * and fail to err.
    +     */    
    +    public static void loadClasses(String[] args, StringBuffer out,
    +                                   StringBuffer err) {
    +        if (null != args) {
    +            for (int i = 0; i < args.length; i++) {
    +                try {
    +                    Class c = Class.forName(args[i]);
    +                    if (null != out) {
    +                        out.append("\n");
    +                        out.append(args[i]);
    +                        out.append(": ");
    +                        out.append(c.getName());
    +                    }
    +                } catch (Throwable t) {
    +                    if (null != err) {
    +                        err.append("\n");
    +                        FileUtil.render(t, err);
    +                    }
    +                }
    +            } 
    +            
    +        }
    +    } 
    +    
    +    private static String fileToClassname(File f, String prefix) {
    +        // this can safely assume file exists, starts at base, ends with .class
    +        // this WILL FAIL if full path with drive letter on windows 
    +        String path = f.getPath();
    +        if (!path.startsWith(prefix)) {
    +            String err = "!\"" + path + "\".startsWith(\"" + prefix + "\")";
    +            throw new IllegalArgumentException(err);
    +        }
    +        int length = path.length() - ".class".length();
    +        path = path.substring(prefix.length()+1, length);
    +        for (int i = 0; i < SEPS.length; i++) {
    +            path = path.replace(SEPS[i], '.');
    +        } 
    +        return path;
    +    }
    +    
    +    public static void main (String[] args) { // todo remove as testing
    +        StringBuffer err = new StringBuffer();
    +        StringBuffer out = new StringBuffer();
    +        for (int i = 0; i < args.length; i++) {
    +            String[] names = classesIn(new File(args[i]));
    +            System.err.println(args[i] + " -> " + render(names));
    +            loadClasses(names, out, err); 
    +        }
    +        if (0 < err.length()) {
    +            System.err.println(err.toString());
    +        }
    +        if (0 < out.length()) {
    +            System.out.println(out.toString());
    +        }
    +    } 
    +
    +    public static String render (String[] args) { // todo move as testing
    +        if ((null == args) || (1 > args.length)) {
    +            return "[]";
    +        }
    +        boolean longFormat = (args.length < 10);
    +        String sep = (longFormat ? ", " : "\n\t");
    +        StringBuffer sb = new StringBuffer();
    +        if (!longFormat) sb.append("[");
    +        for (int i = 0; i < args.length; i++) {
    +            if (0 < i) sb.append(sep);
    +            sb.append(args[i]);
    +        } 
    +        sb.append(longFormat ? "\n" : "]");
    +        return sb.toString();
    +    } 
    +    
    +   
    +    /**
    +     * @param thrown the Throwable to render
    +     */
    +    public static String debugStr(Throwable thrown) {
    +        if (null == thrown) {
    +            return "((Throwable) null)";
    +        } else if (thrown instanceof InvocationTargetException) {
    +            return debugStr(((InvocationTargetException)thrown).getTargetException());
    +        } else if (thrown instanceof AbortException) {
    +            IMessage m = ((AbortException) thrown).getIMessage();
    +            if (null != m) {
    +                return "" + m;
    +            }
    +        }
    +        StringWriter buf = new StringWriter();
    +        PrintWriter writer = new PrintWriter(buf);
    +        writer.println(thrown.getMessage());
    +        thrown.printStackTrace(writer);
    +        try { buf.close(); } 
    +        catch (IOException ioe) {} 
    +        return buf.toString();
    +    }
    +
    +    /**
    +     * debugStr(o, false);
    +     * @param source the Object to render
    +     */
    +    public static String debugStr(Object o) {
    +        return debugStr(o, false);
    +    }
    +    
    +    /**
    +     * Render standard debug string for an object in normal, default form.
    +     * @param source the Object to render
    +     * @param recurse if true, then recurse on all non-primitives unless rendered
    +     */
    +    public static String debugStr(Object o, boolean recurse) {
    +        if (null == o) {
    +            return "null";
    +        } else if (recurse) {
    +            ArrayList rendering = new ArrayList();
    +            rendering.add(o);
    +            return debugStr(o, rendering);
    +        } else {
    +            Class c = o.getClass();
    +            Field[] fields = c.getDeclaredFields();
    +            Object[] values = new Object[fields.length];
    +            String[] names = new String[fields.length];
    +            for (int i = 0; i < fields.length; i++) {
    +				Field field = fields[i];
    +				names[i] = field.getName();
    +                try {
    +                    values[i] = field.get(o);
    +                    if (field.getType().isArray()) {
    +                        List list = org.aspectj.util.LangUtil.arrayAsList((Object[]) values[i]);
    +                        values[i] = list.toString();
    +                    }
    +                } catch (IllegalAccessException e) {
    +                    values[i] = "";
    +                }
    +			}
    +            return debugStr(c, names, values);
    +        }
    +    }
    +
    +    /**
    +     * recursive variant avoids cycles.
    +     * o added to rendering before call.
    +     */
    +    private static String debugStr(Object o, ArrayList rendering) {
    +        if (null == o) {
    +            return "null";
    +        } else if (!rendering.contains(o)) {
    +            throw new Error("o not in rendering");
    +        }
    +        Class c = o.getClass();
    +        if (c.isArray()) {
    +            Object[] ra = (Object[]) o;
    +            StringBuffer sb = new StringBuffer();
    +            sb.append("[");
    +            for (int i = 0; i < ra.length; i++) {
    +                if (i > 0) {
    +                    sb.append(", ");
    +                }
    +                rendering.add(ra[i]);
    +				sb.append(debugStr(ra[i], rendering));
    +			}
    +            sb.append("]");
    +            return sb.toString();
    +        }
    +        Field[] fields = nonStaticFields(c.getFields());
    +        Object[] values = new Object[fields.length];
    +        String[] names = new String[fields.length];
    +        for (int i = 0; i < fields.length; i++) {
    +            Field field = fields[i];
    +            names[i] = field.getName();
    +            // collapse to String
    +            Object value = privilegedGetField(field,o);
    +            if (null == value) { 
    +                values[i] = "null";
    +            } else if (rendering.contains(value)) {
    +                values[i] = "";
    +            } else {
    +                rendering.add(value);
    +                values[i] = debugStr(value, rendering);
    +            }
    +        }
    +        return debugStr(c, names, values);
    +    }
    +
    +    /** incomplete - need protection domain */
    +    private static Object privilegedGetField(final Field field, final Object o) {
    +        try {
    +            return AccessController.doPrivileged(new PrivilegedExceptionAction() {
    +                public Object run() {
    +                    try {
    +                        return field.get(o);
    +                    } catch(IllegalAccessException e) {
    +                        return "";
    +                    }
    +                }
    +            });
    +        } catch (PrivilegedActionException e) {
    +            return "";
    +        }               
    +    }
    +    
    +    private static Field[] nonStaticFields(Field[] fields) {
    +        if (null == fields) {
    +            return new Field[0];
    +        }
    +        int to = 0;
    +        int from = 0;
    +        while (from < fields.length) {
    +            if (!Modifier.isStatic(fields[from].getModifiers())) {
    +                if (to != from) {
    +                    fields[to] = fields[from];
    +                }
    +                to++;
    +            }
    +            from++;
    +        }
    +        if (to < from) {
    +            Field[] result = new Field[to];
    +            if (to > 0) {
    +                System.arraycopy(fields, 0, result, 0, to);
    +            }
    +            fields = result;
    +        }
    +        return fields;
    +    }
    +
    +    /**  debugStr(source, names, items, null, null, null, null) */ 
    +    public static String debugStr(Class source, String[] names, Object[] items) {
    +        return debugStr(source, null, names, null, items, null, null);
    +    }
    +
    +    /**
    +     * Render standard debug string for an object.
    +     * This is the normal form and an example with the default values:
    +     * {className}{prefix}{{name}{infix}{value}{delimiter}}..{suffix}
    +     * Structure[head=root, tail=leaf]
    + * Passing null for the formatting entries provokes the default values, + * so to print nothing, you should pass "". Default values:
    +     *    prefix: "["   SPLIT_START
    +     *     infix: "="
    +     * delimiter: ", "  SPLIT_DELIM
    +     *    suffix: "]"   SPLIT_END
    +     * @param source the Class prefix to render unqualified - omitted if null
    +     * @param names the String[] (field) names of the items - omitted if null
    +     * @param items the Object[] (field) values
    +     * @param prefix the String to separate classname and start of name/values
    +     * @param delimiter the String to separate name/value instances
    +     * @param infix the String to separate name and value
    +     *         used only if both name and value exist
    +     * @param suffix the String to delimit the end of the name/value instances
    +     *         used only if classname exists
    +     */
    +    public static String debugStr(Class source, String prefix, String[] names, 
    +        String infix, Object[] items, String delimiter,  String suffix) {
    +
    +        if (null == delimiter) {
    +            delimiter = SPLIT_DELIM;
    +        }
    +        if (null == prefix) {
    +            prefix = SPLIT_START;
    +        }
    +        if (null == infix) {
    +            infix = "=";
    +        }
    +        if (null == suffix) {
    +            suffix = SPLIT_END;
    +        }
    +        StringBuffer sb = new StringBuffer();
    +        if (null != source) {
    +            sb.append(org.aspectj.util.LangUtil.unqualifiedClassName(source));
    +        }
    +        sb.append(prefix);
    +        if (null == names) {
    +            names = NONE;
    +        }
    +        if (null == items) {
    +            items = NONE;
    +        }
    +        final int MAX 
    +            = (names.length > items.length ? names.length : items.length);
    +        for (int i = 0; i < MAX; i++) {
    +			if (i > 0) {
    +                sb.append(delimiter);
    +            }
    +            if (i < names.length) {
    +                sb.append(names[i]);
    +            }
    +            if (i < items.length) {
    +                if (i < names.length) {
    +                    sb.append(infix);
    +                }
    +                sb.append(items[i] + "");
    +            }            
    +		}
    +        sb.append(suffix);
    +        return sb.toString();
    +    }
    +
    +    /** 
    +     * @return a String with the unqualified class name of the object (or "null")
    +     */
    +    public static String unqualifiedClassName(Object o) {
    +        return unqualifiedClassName(null == o ? null : o.getClass());
    +    }
    +
    +    /** 
    +     * @return a String with the unqualified class name of the class (or "null")
    +     */
    +    public static String unqualifiedClassName(Class c) {
    +        if (null == c) {
    +            return "null";
    +        }
    +        String name = c.getName();
    +        int loc = name.lastIndexOf(".");
    +        if (-1 != loc)
    +            name = name.substring(1 + loc);
    +        return name;
    +    }
    +    
    +    /** 
    +     * Calculate exact diffs and report missing and extra items.
    +     * This assumes the input List are not modified concurrently.
    +     * @param expectedListIn the List of expected results - treated as empty if null
    +     * @param actualListIn the List of actual results - treated as empty if null
    +     * @param extraListOut the List for any actual results not expected - ignored if null
    +     * @param missingListOut the List for any expected results not found - ignored if null
    +     * */
    +	public static void makeDiffs(
    +		List expectedListIn,
    +		List actualListIn,
    +		List missingListOut,
    +        List extraListOut) {
    +		if ((null == missingListOut) && (null == extraListOut)) {
    +			return;
    +		}
    +		if (null == expectedListIn) {
    +			expectedListIn = Collections.EMPTY_LIST;
    +		}
    +		if (null == actualListIn) {
    +			actualListIn = Collections.EMPTY_LIST;
    +		}
    +        if ((0 == actualListIn.size()) && (0 == expectedListIn.size()) ) {
    +            return;
    +        }
    +		BitSet actualExpected = new BitSet();
    +		for (int i = 0; i < expectedListIn.size(); i++) {
    +			Object expect = expectedListIn.get(i);
    +			int loc = actualListIn.indexOf(expect);
    +			if (-1 == loc) {
    +				if (null != missingListOut) {
    +					missingListOut.add(expect);
    +				}
    +			} else {
    +				actualExpected.set(loc);
    +			}
    +		}
    +		if (null != extraListOut) {
    +			for (int i = 0; i < actualListIn.size(); i++) {
    +				if (!actualExpected.get(i)) {
    +					extraListOut.add(actualListIn.get(i));
    +				}
    +			}
    +		}
    +	}
    +    // XXX unit test for makeSoftDiffs
    +    /** 
    +     * Calculate potentially "soft" diffs using 
    +     * Comparator.compare(expected, actual).
    +     * This shallow-copies and sorts the input Lists.
    +     * @param expectedListIn the List of expected results - treated as empty if null
    +     * @param actualListIn the List of actual results - treated as empty if null
    +     * @param extraListOut the List for any actual results not expected - ignored if null
    +     * @param missingListOut the List for any expected results not found - ignored if null
    +     * @param comparator the Comparator for comparisons - not null
    +     * @throws IllegalArgumentException if comp is null
    +     */
    +    public static void makeSoftDiffs(  // XXX no intersect or union on collections???
    +        List expectedListIn,
    +        List actualListIn,
    +        List missingListOut,
    +        List extraListOut,
    +        Comparator comparator) {
    +        if ((null == missingListOut) && (null == extraListOut)) {
    +            return;
    +        }
    +        if (null == comparator) {
    +            throw new IllegalArgumentException("null comparator");
    +        }
    +        if (null == expectedListIn) {
    +            expectedListIn = Collections.EMPTY_LIST;
    +        }
    +        if (null == actualListIn) {
    +            actualListIn = Collections.EMPTY_LIST;
    +        }
    +        if ((0 == actualListIn.size()) && (0 == expectedListIn.size()) ) {
    +            return;
    +        }
    +        
    +        ArrayList expected = new ArrayList();
    +        expected.addAll(expectedListIn);
    +        Collections.sort(expected, comparator);
    +        Iterator expectedIter = expected.iterator();
    +        Object exp = null;
    +        
    +        ArrayList actual = new ArrayList();
    +        actual.addAll(actualListIn);
    +        Collections.sort(actual, comparator);        
    +        Iterator actualIter = actual.iterator();        
    +        Object act = null;
    +        
    +        while (((null != act) || actualIter.hasNext())
    +             && ((null != exp) || expectedIter.hasNext())) {
    +            if (null == act) {
    +                act = actualIter.next();
    +            }
    +            if (null == exp) {
    +                exp = expectedIter.next();
    +            }
    +            int diff = comparator.compare(exp, act);
    +            if (0 > diff) {          // exp < act
    +                if (null != missingListOut) {
    +                    missingListOut.add(exp);
    +                    exp = null;
    +                }
    +            } else if (0 < diff) {   // exp > act
    +                if (null != extraListOut) {
    +                    extraListOut.add(act);
    +                    act = null;
    +                }
    +            } else { // got match of actual to expected
    +                // absorb all actual matching expected (duplicates)
    +                while ((0 == diff) && actualIter.hasNext()) {
    +                    act = actualIter.next();
    +                    diff = comparator.compare(exp, act);
    +                }
    +                if (0 == diff) {
    +                    act = null;
    +                }
    +                exp = null;
    +            }
    +        }
    +        if (null != missingListOut) {
    +             if (null != exp) {
    +                missingListOut.add(exp);
    +             }
    +            while (expectedIter.hasNext()) {
    +                 missingListOut.add(expectedIter.next());
    +            }
    +        }
    +        if (null != extraListOut) {
    +            if (null != act) {
    +                extraListOut.add(act);
    +            }
    +            while (actualIter.hasNext()) {
    +                 extraListOut.add(actualIter.next());
    +            }
    +        }
    +    }    
    +    public static class FlattenSpec {
    +        /** 
    +         * This tells unflatten(..) to throw IllegalArgumentException
    +         * if it finds two contiguous delimiters.
    +         */
    +        public static final String UNFLATTEN_EMPTY_ERROR 
    +            = "empty items not permitted when unflattening";
    +        /** 
    +         * This tells unflatten(..) to skip empty items when unflattening
    +         * (since null means "use null")
    +         */
    +        public static final String UNFLATTEN_EMPTY_AS_NULL 
    +            = "unflatten empty items as null";
    +            
    +        /** 
    +         * This tells unflatten(..) to skip empty items when unflattening
    +         * (since null means "use null")
    +         */
    +        public static final String SKIP_EMPTY_IN_UNFLATTEN
    +            = "skip empty items when unflattening";
    +        
    +        /** 
    +         * For Ant-style attributes: "item,item" (with escaped commas).
    +         * There is no way when unflattening to distinguish 
    +         * values which were empty from those which were null,
    +         * so all are unflattened as empty.
    +         */
    +        public static final FlattenSpec COMMA
    +            = new FlattenSpec(null, "", "\\", ",", null, "") {
    +                public String toString() { return "FlattenSpec.COMMA"; }
    +            };
    +            
    +        /** this attempts to mimic ((List)l).toString() */
    +        public static final FlattenSpec LIST
    +            = new FlattenSpec("[", "", null, ", ", "]", UNFLATTEN_EMPTY_ERROR) {
    +                public String toString() { return "FlattenSpec.LIST"; }
    +            };
    +            
    +        /** how toString renders null values */
    +        public static final String NULL = "";
    +        private static String r(String s) {
    +            return (null == s ? NULL : s);
    +        }
    +
    +        public final String prefix;
    +        public final String nullFlattened;
    +        public final String escape;
    +        public final String delim;
    +        public final String suffix;
    +        public final String emptyUnflattened;
    +        private transient String toString; 
    +
    +        public FlattenSpec(
    +            String prefix, 
    +            String nullRendering, 
    +            String escape,
    +            String delim, 
    +            String suffix,
    +            String emptyUnflattened) {
    +            this.prefix = prefix;
    +            this.nullFlattened = nullRendering;
    +            this.escape = escape;
    +            this.delim = delim;
    +            this.suffix = suffix;
    +            this.emptyUnflattened = emptyUnflattened;
    +            throwIaxIfNull(emptyUnflattened, "use UNFLATTEN_EMPTY_AS_NULL");
    +        }
    +        
    +        public String toString() {
    +            if (null == toString) {
    +                toString = "FlattenSpec("
    +                    + "prefix=" + r(prefix)
    +                    + ", nullRendering=" + r(nullFlattened)
    +                    + ", escape=" + r(escape)
    +                    + ", delim=" + r(delim)
    +                    + ", suffix=" + r(suffix)
    +                    + ", emptyUnflattened=" + r(emptyUnflattened)
    +                    + ")";
    +            }
    +            return toString;
    +        }
    +    }
    +} // class LangUtil
    +
    +//  --------- java runs using Ant
    +//	/**
    +//	 * Run a Java command separately.
    +//	 * @param className the fully-qualified String name of the class 
    +//     *         with the main method to run
    +//	 * @param classpathFiles the File to put on the classpath
    +//	 * @param args to the main method of the class
    +//     * @param outSink the PrintStream for the output stream - may be null
    +//	 */
    +//	public static void oldexecuteJava(
    +//		String className,
    +//		File[] classpathFiles,
    +//		String[] args,
    +//        PrintStream outSink) {
    +//        Project project = new Project();
    +//        project.setName("LangUtil.executeJava(" + className + ")");
    +//        Path classpath = new Path(project, classpathFiles[0].getAbsolutePath());
    +//        for (int i = 1; i < classpathFiles.length; i++) {
    +//			classpath.addExisting(new Path(project, classpathFiles[i].getAbsolutePath()));
    +//		}        
    +//
    +//        Commandline cmds = new Commandline();
    +//        cmds.addArguments(new String[] {className});
    +//        cmds.addArguments(args);      
    +//
    +//        ExecuteJava runner = new ExecuteJava();
    +//        runner.setClasspath(classpath);
    +//        runner.setJavaCommand(cmds);        
    +//        if (null != outSink) {
    +//            runner.setOutput(outSink); // XXX todo
    +//        }
    +//        runner.execute(project);
    +//	}
    +
    +//     public static void executeJava(
    +//        String className,
    +//        File dir,
    +//        File[] classpathFiles,
    +//        String[] args,
    +//        PrintStream outSink) {
    +//        StringBuffer sb = new StringBuffer();
    +//        
    +//        sb.append("c:/apps/jdk1.3.1/bin/java.exe -classpath \"");
    +//        for (int i = 0; i < classpathFiles.length; i++) {
    +//            if (i < 0) {
    +//                sb.append(";");
    +//            }
    +//            sb.append(classpathFiles[i].getAbsolutePath());            
    +//        }        
    +//        sb.append("\" -verbose " + className);
    +//        for (int i = 0; i < args.length; i++) {
    +//            sb.append(" " + args[i]);
    +//		}
    +//        Exec exec = new Exec();
    +//        Project project = new Project();
    +//        project.setProperty("ant.home", "c:/home/wes/aj/aspectj/modules/lib/ant");
    +//        System.setProperty("ant.home", "c:/home/wes/aj/aspectj/modules/lib/ant");
    +//        exec.setProject(new Project());
    +//        exec.setCommand(sb.toString());
    +//        exec.setDir(dir.getAbsolutePath());
    +//        exec.execute();
    +//    }
    +//     public static void execJavaProcess(
    +//        String className,
    +//        File dir,
    +//        File[] classpathFiles,
    +//        String[] args,
    +//        PrintStream outSink) throws Throwable {
    +//        StringBuffer sb = new StringBuffer();
    +//        
    +//        sb.append("c:\\apps\\jdk1.3.1\\bin\\java.exe -classpath \"");
    +//        for (int i = 0; i < classpathFiles.length; i++) {
    +//            if (i > 0) {
    +//                sb.append(";");
    +//            }
    +//            sb.append(classpathFiles[i].getAbsolutePath());            
    +//        }        
    +//        sb.append("\" -verbose " + className);
    +//        for (int i = 0; i < args.length; i++) {
    +//            sb.append(" " + args[i]);
    +//        }
    +//        String command = sb.toString();
    +//        System.err.println("launching process: " + command);
    +//        Process process = Runtime.getRuntime().exec(command);
    +//        // huh? err/out
    +//        InputStream errStream = null; 
    +//        InputStream outStream = null; 
    +//        Throwable toThrow = null;
    +//        int result = -1;
    +//        try {
    +//            System.err.println("waiting for process: " + command);
    +//            errStream = null; // process.getErrorStream();
    +//            outStream = null; // process.getInputStream(); // misnamed - out
    +//            result = process.waitFor();
    +//            System.err.println("Done waiting for process: " + command);
    +//            process.destroy();
    +//        } catch (Throwable t) {
    +//            toThrow = t;
    +//        } finally {
    +//            if (null != outStream) {
    +//                FileUtil.copy(outStream, System.out, false);
    +//                try { outStream.close(); }
    +//                catch (IOException e) {}
    +//            }
    +//            if (null != errStream) {
    +//                FileUtil.copy(errStream, System.err, false);
    +//                try { errStream.close(); }
    +//                catch (IOException e) {}
    +//            }
    +//        }
    +//        if (null != toThrow) {
    +//            throw toThrow;
    +//        }
    +//    }
    +//        try {
    +//            // show the command
    +//            log(command, Project.MSG_VERBOSE);
    +//
    +//            // exec command on system runtime
    +//            Process proc = Runtime.getRuntime().exec(command);
    +//
    +//            if (out != null)  {
    +//                fos = new PrintWriter(new FileWriter(out));
    +//                log("Output redirected to " + out, Project.MSG_VERBOSE);
    +//            }
    +//
    +//            // copy input and error to the output stream
    +//            StreamPumper inputPumper =
    +//                new StreamPumper(proc.getInputStream(), Project.MSG_INFO);
    +//            StreamPumper errorPumper =
    +//                new StreamPumper(proc.getErrorStream(), Project.MSG_WARN);
    +//
    +//            // starts pumping away the generated output/error
    +//            inputPumper.start();
    +//            errorPumper.start();
    +//
    +//            // Wait for everything to finish
    +//            proc.waitFor();
    +//            inputPumper.join();
    +//            errorPumper.join();
    +//            proc.destroy();
    +//
    +//            // close the output file if required
    +//            logFlush();
    +//
    +//            // check its exit value
    +//            err = proc.exitValue();
    +//            if (err != 0) {
    +//                if (failOnError) {
    +//                    throw new BuildException("Exec returned: " + err, getLocation());
    +//                } else {
    +//                    log("Result: " + err, Project.MSG_ERR);
    +//                }
    +//            }
    +//        } catch (IOException ioe) {
    +//            throw new BuildException("Error exec: " + command, ioe, getLocation());
    +//        } catch (InterruptedException ex) {}
    +//
    +
    +
    diff --git a/testing/src/org/aspectj/testing/util/LineReader.java b/testing/src/org/aspectj/testing/util/LineReader.java
    new file mode 100644
    index 000000000..67f971022
    --- /dev/null
    +++ b/testing/src/org/aspectj/testing/util/LineReader.java
    @@ -0,0 +1,204 @@
    +/* *******************************************************************
    + * Copyright (c) 1999-2001 Xerox Corporation, 
    + *               2002 Palo Alto Research Center, Incorporated (PARC).
    + * All rights reserved. 
    + * This program and the accompanying materials are made available 
    + * under the terms of the Common Public License v1.0 
    + * which accompanies this distribution and is available at 
    + * http://www.eclipse.org/legal/cpl-v10.html 
    + *  
    + * Contributors: 
    + *     Xerox/PARC     initial implementation 
    + * ******************************************************************/
    +
    +
    +/*
    + * LineReader.java created on May 3, 2002
    + *
    + */
    +package org.aspectj.testing.util;
    +
    +import java.io.File;
    +import java.io.FileReader;
    +import java.io.IOException;
    +import java.io.LineNumberReader;
    +import java.util.ArrayList;
    +
    +/** LineNumberReader which absorbs our lines and renders as file:line */
    +public class LineReader extends LineNumberReader {
    +    /** delimited multi-line output of readToBlankLine */
    +    public static final String RETURN= "\n\r";
    +    
    +    private static final String[] NONE = new String[0];
    +    private static final String cSCRIPT = "#";
    +    private static final String cJAVA = "//";
    +    private static final String[] TESTER_LEAD = new String[] {cSCRIPT, cJAVA};
    +    
    +    /** 
    +     * Convenience factory for tester suite files
    +     * @return null if IOException or IllegalArgumentException thrown
    +     */
    +    public static final LineReader createTester(File file) {
    +        return create(file, TESTER_LEAD, null);
    +    }
    +    
    +    /** 
    +     * convenience factory 
    +     * @return null if IOException or IllegalArgumentException thrown
    +     */
    +    public static final LineReader create(File file, 
    +        String[] leadComments, String[] eolComments) {
    +        try {
    +            FileReader reader = new FileReader(file);
    +            return new LineReader(reader, file, leadComments, eolComments);
    +        } catch (IllegalArgumentException e) {
    +        } catch (IOException e) {
    +        }
    +        return null;
    +    }
    +    
    +    final private File file;
    +    final private String[] eolComments;
    +    final private String[] leadComments;
    +    
    +    /**
    +     * @param file the File used to open the FileReader
    +     * @param leadComments the String[] to be taken as the start of
    +     * comments when they are the first non-blank text on a line -
    +     * pass null to signal none.
    +     * @param leadComments the String[] to be taken as the start of
    +     * comment anywhere on a line - pass null to signal none.
    +     *@throws IllegalArgumentException if any String in
    +     * leadComments or eolComments is null.
    +     */
    +    public LineReader(FileReader reader, File file, 
    +        String[] leadComments, String[] eolComments) {
    +        super(reader); 
    +        this.file = file;
    +        this.eolComments = normalize(eolComments);
    +        this.leadComments = normalize(leadComments);
    +    }
    +    public LineReader(FileReader reader, File file) {
    +        this(reader, file, null, null);
    +    }
    +    
    +    /** @return file:line */
    +    public String toString() {
    +        return file.getPath() + ":" + getLineNumber();
    +    }
    +    
    +    /** @return underlying file */
    +    public File getFile() { return file; }
    +
    +    /**
    +     * Reader first..last (inclusive) and return in String[].
    +     * This will return (1+(last-first)) elements only if this
    +     * reader has not read past the first line and there are last lines
    +     * and there are no IOExceptions during reads.
    +     * @param first the first line to read - if negative, use 0
    +     * @param last the last line to read (inclusive) 
    +     *         - if less than first, use first
    +     * @return String[] of first..last (inclusive) lines read or 
    +     */
    +    public String[] readLines(int first, int last) {
    +        if (0 > first) first = 0;
    +        if (first > last) last = first;
    +        ArrayList list = new ArrayList();
    +        try {
    +            String line = null;
    +            while (getLineNumber() < first) { 
    +                line = readLine();
    +                if (null == line) {
    +                    break; 
    +                }
    +            }
    +            if (getLineNumber() > first) { 
    +                // XXX warn? something else read past line
    +            }
    +            if ((null != line) && (first == getLineNumber())) {
    +                list.add(line);
    +                while (last >= getLineNumber()) {
    +                    line = readLine();
    +                    if (null == line) {
    +                        break;
    +                    }
    +                    list.add(line);
    +                }
    +            }
    +        } catch (IOException e) {
    +            return NONE;
    +        }
    +        return (String[]) list.toArray(NONE);
    +    }
    +    
    +    /** Skip to next blank line 
    +     * @return the String containing all lines skipped (delimited with RETURN)
    +     */
    +    public String readToBlankLine() throws IOException {
    +        StringBuffer sb = new StringBuffer();
    +        String input;
    +        while (null != (input = nextLine(false))) { // get next empty line to restart
    +            sb.append(input);
    +            sb.append(RETURN);// XXX verify/ignore/correct
    +        }
    +        return sb.toString();
    +    }
    +
    +    /**
    +     * Get the next line from the input stream, stripping eol and
    +     * leading comments.
    +     * If emptyLinesOk is true, then this reads past lines which are
    +     * empty after omitting comments and trimming until the next non-empty line.
    +     * Otherwise, this returns null on reading an empty line.
    +     * (The input stream is not exhausted until this
    +     * returns null when emptyLines is true.)
    +     * @param emptyLinesOk if false, return null if the line is empty
    +     * @return next non-null, non-empty line in reader,
    +     * ignoring comments
    +     */
    +    public String nextLine(boolean emptyLinesOk) throws IOException {
    +        int len = 0;
    +        String result = null;
    +        do {
    +            result = readLine();
    +            if (result == null)
    +                return null;
    +            result = result.trim();
    +            for (int i = 0; i < eolComments.length; i++) {
    +                int loc = result.indexOf(eolComments[i]);
    +                if (-1 != loc) {
    +                    result = result.substring(0, loc);
    +                    break;
    +                }
    +            }
    +            len = result.length();
    +            if (0 < len) {
    +                for (int i = 0; i < leadComments.length; i++) {
    +                    if (result.startsWith(leadComments[i])) {
    +                        result = "";
    +                        break;
    +                    }
    +                }
    +                len = result.length();
    +            }
    +            len = result.length();
    +            if (!emptyLinesOk && (0 == len))
    +                return null;
    +        } while (0 == len);
    +        return result;
    +    }
    +
    +    private String[] normalize(String[] input) {
    +        if ((null == input) || (0 == input.length)) return NONE;
    +        String[] result = new String[input.length];
    +        System.arraycopy(input, 0, result, 0, result.length);
    +        for (int i = 0; i < result.length; i++) {
    +            if ((null == result[i]) || (0 == result[i].length())) {
    +                throw new IllegalArgumentException("empty input at [" + i + "]");
    +            }
    +        }
    +        return result;
    +    }
    +
    +}
    +
    diff --git a/testing/src/org/aspectj/testing/util/Node.java b/testing/src/org/aspectj/testing/util/Node.java
    new file mode 100644
    index 000000000..11d197eb2
    --- /dev/null
    +++ b/testing/src/org/aspectj/testing/util/Node.java
    @@ -0,0 +1,162 @@
    +/* *******************************************************************
    + * Copyright (c) 1999-2001 Xerox Corporation, 
    + *               2002 Palo Alto Research Center, Incorporated (PARC).
    + * All rights reserved. 
    + * This program and the accompanying materials are made available 
    + * under the terms of the Common Public License v1.0 
    + * which accompanies this distribution and is available at 
    + * http://www.eclipse.org/legal/cpl-v10.html 
    + *  
    + * Contributors: 
    + *     Xerox/PARC     initial implementation 
    + * ******************************************************************/
    +
    +
    +/*
    + * Node.java created on May 14, 2002
    + *
    + */
    +package org.aspectj.testing.util;
    +
    +
    +import java.lang.reflect.Array;
    +import java.util.ArrayList;
    +import java.util.List;
    +
    +/** 
    +  * A node in a tree containing other Node or SpecElements items.
    +  */
    +public class Node { // XXX render
    +    public static final Node[] EMPTY_NODES = new Node[0];
    +    public static final Object[] EMPTY_ITEMS = new Object[0];
    +
    +    /** 
    +     * Visit all the SpecElements (and Node) reachable from node
    +     * in depth-first order, halting if checker objects.
    +     * @param node the Node to pass to checker
    +     * @param itemChecker the ObjectChecker to pass items to
    +     * @param nodeVisitor if not null, then use instead of recursing
    +     * @return false on first objection, true otherwise
    +     * @throws IllegalArgumentExcpetion if checker is null
    +     */
    +    public static final boolean visit(Node node, ObjectChecker itemChecker,
    +                                         ObjectChecker nodeVisitor) {
    +        if (null == node) {
    +            return (null == itemChecker ? true : itemChecker.isValid(null));
    +        }
    +        boolean result = true;
    +
    +        Node[] nodes = node.getNodes();
    +        for (int i = 0; result && (i < nodes.length); i++) {
    +            result = (null == nodeVisitor 
    +                    ? visit(nodes[i], itemChecker, null)
    +                    : nodeVisitor.isValid(nodes[i]));
    +        }
    +        if (result) {
    +            Object[] elements = node.getItems();
    +            for (int i = 0; result && (i < elements.length); i++) {
    +                result = itemChecker.isValid(elements[i]);
    +            }
    +        }
    +
    +        return result;
    +    }
    +
    +    public final String name;
    +    public final Class type;
    +    public final Object key;
    +    protected final Object[] typeArray;
    +    protected final List nodes;
    +    protected final List items;
    +
    +    public Node() {
    +        this("Node");
    +    }
    +
    +    public Node(String name) {
    +        this(name, null);
    +    }
    +
    +    /** use the name as the key */
    +    public Node(String name, Class type) {
    +        this(name, type, name);
    +    }
    +    /**  */
    +    public Node(String name, Class type, Object key) {    
    +        if (null == name) {
    +            throw new IllegalArgumentException("null name");
    +        }
    +        if (null == key) {
    +            throw new IllegalArgumentException("null key");
    +        }
    +        this.name = name;
    +        this.type = type;
    +        this.key = key;
    +        nodes = new ArrayList();
    +        items = new ArrayList();
    +        if (type == null) {
    +            type = Object.class;
    +        }
    +        typeArray = (Object[]) Array.newInstance(type, 0);
    +    }
    +
    +    /** 
    +     * clear all items and nodes.
    +     */
    +    public void clear() { // XXX synchronize
    +        nodes.clear();
    +        items.clear();
    +    }
    +    
    +    /**
    +     * Add item to list of items
    +     * unless it is null, of the wrong type, or the collection fails to add
    +     * @return true if added
    +     */
    +    public boolean add(Object item) {
    +        if (null == item)
    +            throw new IllegalArgumentException("null item");
    +        if ((null != type) && (!type.isAssignableFrom(item.getClass()))) {
    +            return false;
    +        }
    +        return items.add(item);
    +    }
    +
    +    /**
    +     * Add node to list of nodes
    +     * unless it is null, of the wrong type, or the collection fails to add
    +     * @return true if added
    +     */
    +    public boolean addNode(Node node) {
    +        if (null == node) {
    +            throw new IllegalArgumentException("null node");
    +        }
    +        return nodes.add(node);
    +    }
    +
    +    /**
    +     * Get the current list of nodes - never null
    +     */
    +    public Node[] getNodes() {
    +        if ((null == nodes) || (1 > nodes.size())) {
    +            return EMPTY_NODES;
    +        }
    +        return (Node[]) nodes.toArray(EMPTY_NODES);
    +    }
    +
    +    /**
    +     * Get the current list of items - never null
    +     * @return items in current list, cast to type[] if type was not null
    +     */
    +    public Object[] getItems() {
    +        if ((null == items) || (1 > items.size())) {
    +            return EMPTY_ITEMS;
    +        }
    +        return items.toArray(typeArray);
    +    }
    +
    +    /** @return name */
    +    public String toString() {
    +        return name;
    +    }
    +}
    diff --git a/testing/src/org/aspectj/testing/util/NullPrintStream.java b/testing/src/org/aspectj/testing/util/NullPrintStream.java
    new file mode 100644
    index 000000000..7ba8e28f9
    --- /dev/null
    +++ b/testing/src/org/aspectj/testing/util/NullPrintStream.java
    @@ -0,0 +1,95 @@
    +/* *******************************************************************
    + * Copyright (c) 1999-2001 Xerox Corporation, 
    + *               2002 Palo Alto Research Center, Incorporated (PARC).
    + * All rights reserved. 
    + * This program and the accompanying materials are made available 
    + * under the terms of the Common Public License v1.0 
    + * which accompanies this distribution and is available at 
    + * http://www.eclipse.org/legal/cpl-v10.html 
    + *  
    + * Contributors: 
    + *     Xerox/PARC     initial implementation 
    + * ******************************************************************/
    +
    +
    +/*
    + * NullPrintStream.java created on May 29, 2002
    + *
    + */
    +package org.aspectj.testing.util;
    +
    +import java.io.OutputStream;
    +import java.io.PrintStream;
    +
    +/**
    + * Ignore any output to a NullPrintStream.
    + * Clients use singleton NULL_PrintStream or NULL_OutputStream.
    + * @author isberg
    + */
    +public final class NullPrintStream extends PrintStream {
    +
    +    public static final OutputStream NULL_OutputStream = NullOutputStream.ME;
    +    public static final PrintStream NULL_PrintStream = new NullPrintStream();
    +
    +    private NullPrintStream() {
    +        super(NULL_OutputStream);
    +    }
    +    public void write(int b) {
    +    }
    +    public void write(byte[] b) {
    +    }
    +    public void write(byte[] b, int off, int len) {
    +    }
    +    public void print(boolean arg0) {
    +    }
    +    public void print(char arg0) {
    +    }
    +    public void print(char[] arg0) {
    +    }
    +    public void print(double arg0) {
    +    }
    +    public void print(float arg0) {
    +    }
    +    public void print(int arg0) {
    +    }
    +    public void print(long arg0) {
    +    }
    +    public void print(Object arg0) {
    +    }
    +    public void print(String arg0) {
    +    }
    +    public void println() {
    +    }
    +    public void println(boolean arg0) {
    +    }
    +    public void println(char arg0) {
    +    }
    +    public void println(char[] arg0) {
    +    }
    +    public void println(double arg0) {
    +    }
    +    public void println(float arg0) {
    +    }
    +    public void println(int arg0) {
    +    }
    +    public void println(long arg0) {
    +    }
    +    public void println(Object arg0) {
    +    }
    +    public void println(String arg0) {
    +    }
    +
    +}
    +
    +final class NullOutputStream extends OutputStream {
    +    static final OutputStream ME = new NullOutputStream();
    +
    +    private NullOutputStream() {
    +    }
    +    public void write(int b) {
    +    }
    +    public void write(byte[] b) {
    +    }
    +    public void write(byte[] b, int off, int len) {
    +    }
    +}
    diff --git a/testing/src/org/aspectj/testing/util/ObjectChecker.java b/testing/src/org/aspectj/testing/util/ObjectChecker.java
    new file mode 100644
    index 000000000..e7d42755a
    --- /dev/null
    +++ b/testing/src/org/aspectj/testing/util/ObjectChecker.java
    @@ -0,0 +1,38 @@
    +/* *******************************************************************
    + * Copyright (c) 1999-2001 Xerox Corporation, 
    + *               2002 Palo Alto Research Center, Incorporated (PARC).
    + * All rights reserved. 
    + * This program and the accompanying materials are made available 
    + * under the terms of the Common Public License v1.0 
    + * which accompanies this distribution and is available at 
    + * http://www.eclipse.org/legal/cpl-v10.html 
    + *  
    + * Contributors: 
    + *     Xerox/PARC     initial implementation 
    + * ******************************************************************/
    +
    +
    +package org.aspectj.testing.util;
    +
    +/**
    +  * Check input for validity. 
    +  */
    +public interface ObjectChecker {
    +    /** this returns true for any input, even if null */
    +    public static final ObjectChecker ANY = new ObjectChecker() {
    +        public final boolean isValid(Object input) { return true; }
    +        public final String toString() { return "ObjectChecker.ANY"; }
    +    };
    +    /** this returns true for any non-null object */
    +    public static final ObjectChecker NOT_NULL = new ObjectChecker() {
    +        public boolean isValid(Object input) { return (null != input); }
    +        public String toString() { return "ObjectChecker.MOT_NULL"; }
    +    };
    +    
    +    /**
    +     * Check input for validity. 
    +     * @param input the Object to check
    +     * @return true if input is ok 
    +     */
    +    public boolean isValid(Object input);
    +}
    diff --git a/testing/src/org/aspectj/testing/util/ProxyPrintStream.java b/testing/src/org/aspectj/testing/util/ProxyPrintStream.java
    new file mode 100644
    index 000000000..378f58e36
    --- /dev/null
    +++ b/testing/src/org/aspectj/testing/util/ProxyPrintStream.java
    @@ -0,0 +1,104 @@
    +/* *******************************************************************
    + * Copyright (c) 1999-2001 Xerox Corporation, 
    + *               2002 Palo Alto Research Center, Incorporated (PARC).
    + * All rights reserved. 
    + * This program and the accompanying materials are made available 
    + * under the terms of the Common Public License v1.0 
    + * which accompanies this distribution and is available at 
    + * http://www.eclipse.org/legal/cpl-v10.html 
    + *  
    + * Contributors: 
    + *     Xerox/PARC     initial implementation 
    + * ******************************************************************/
    +
    +package org.aspectj.testing.util;
    +
    +import java.io.IOException;
    +import java.io.PrintStream;
    +
    +/** Wrap a delegate PrintStream, permitting output to be suppressed. */
    +public class ProxyPrintStream extends PrintStream {
    +
    +    private final PrintStream delegate;
    +    private boolean hiding;
    +    public ProxyPrintStream(PrintStream delegate ) {
    +        super(NullPrintStream.NULL_OutputStream);
    +        LangUtil.throwIaxIfNull(delegate, "delegate");
    +        this.delegate = delegate;
    +    }
    +    public void hide() {
    +        hiding = true;
    +    }
    +    public void show() {
    +        hiding = false;
    +    }
    +    public boolean isHiding() {
    +        return hiding;
    +    }
    +    public void write(int b) {
    +        if (!hiding) delegate.write(b);
    +    }
    +    public void write(byte[] b) throws IOException {
    +        if (!hiding) delegate.write(b);
    +    }
    +    public void write(byte[] b, int off, int len) {
    +        if (!hiding) delegate.write(b, off, len);
    +    }
    +    public void print(boolean arg0) {
    +        if (!hiding) delegate.print(arg0);
    +    }
    +    public void print(char arg0) {
    +        if (!hiding) delegate.print(arg0);
    +    }
    +    public void print(char[] arg0) {
    +        if (!hiding) delegate.print(arg0);
    +    }
    +    public void print(double arg0) {
    +        if (!hiding) delegate.print(arg0);
    +    }
    +    public void print(float arg0) {
    +        if (!hiding) delegate.print(arg0);
    +    }
    +    public void print(int arg0) {
    +        if (!hiding) delegate.print(arg0);
    +    }
    +    public void print(long arg0) {
    +        if (!hiding) delegate.print(arg0);
    +    }
    +    public void print(Object arg0) {
    +        if (!hiding) delegate.print(arg0);
    +    }
    +    public void print(String arg0) {
    +        if (!hiding) delegate.print(arg0);
    +    }
    +    public void println() {
    +        if (!hiding) delegate.println();
    +    }
    +    public void println(boolean arg0) {
    +        if (!hiding) delegate.println(arg0);
    +    }
    +    public void println(char arg0) {
    +        if (!hiding) delegate.println(arg0);
    +    }
    +    public void println(char[] arg0) {
    +        if (!hiding) delegate.println(arg0);
    +    }
    +    public void println(double arg0) {
    +        if (!hiding) delegate.println(arg0);
    +    }
    +    public void println(float arg0) {
    +        if (!hiding) delegate.println(arg0);
    +    }
    +    public void println(int arg0) {
    +        if (!hiding) delegate.println(arg0);
    +    }
    +    public void println(long arg0) {
    +        if (!hiding) delegate.println(arg0);
    +    }
    +    public void println(Object arg0) {
    +        if (!hiding) delegate.println(arg0);
    +    }
    +    public void println(String arg0) {
    +        if (!hiding) delegate.println(arg0);
    +    }
    +}
    diff --git a/testing/src/org/aspectj/testing/util/RunUtils.java b/testing/src/org/aspectj/testing/util/RunUtils.java
    new file mode 100644
    index 000000000..febfac442
    --- /dev/null
    +++ b/testing/src/org/aspectj/testing/util/RunUtils.java
    @@ -0,0 +1,368 @@
    +/* *******************************************************************
    + * Copyright (c) 1999-2001 Xerox Corporation, 
    + *               2002 Palo Alto Research Center, Incorporated (PARC).
    + * All rights reserved. 
    + * This program and the accompanying materials are made available 
    + * under the terms of the Common Public License v1.0 
    + * which accompanies this distribution and is available at 
    + * http://www.eclipse.org/legal/cpl-v10.html 
    + *  
    + * Contributors: 
    + *     Xerox/PARC     initial implementation 
    + * ******************************************************************/
    +
    +package org.aspectj.testing.util;
    +
    +import org.aspectj.bridge.IMessage;
    +import org.aspectj.bridge.IMessageHandler;
    +import org.aspectj.bridge.IMessageHolder;
    +import org.aspectj.bridge.MessageUtil;
    +import org.aspectj.bridge.MessageUtil.IMessageRenderer;
    +import org.aspectj.testing.harness.bridge.AbstractRunSpec;
    +import org.aspectj.testing.harness.bridge.IRunSpec;
    +import org.aspectj.testing.run.IRunStatus;
    +import org.aspectj.testing.run.RunValidator;
    +import org.aspectj.util.LangUtil;
    +
    +import java.io.PrintStream;
    +import java.util.Iterator;
    +
    +/**
    + * 
    + */
    +public class RunUtils {
    +
    +    /** enable verbose for this an any related AbstractRunSpec children */
    +    public static void enableVerbose(AbstractRunSpec spec) { // instanceof hack
    +        LangUtil.throwIaxIfNull(spec, "spec");
    +        spec.runtime.setVerbose(true);
    +        for (Iterator iter = spec.getChildren().iterator(); iter.hasNext();) {
    +			IRunSpec child = (IRunSpec) iter.next();
    +			if (child instanceof AbstractRunSpec) {
    +                enableVerbose((AbstractRunSpec) child);
    +            }
    +		}
    +    }
    +    
    +    /** 
    +     * Calculate failures for this status.
    +     * If the input status has no children and failed, the result is 1.
    +     * If it has children and recurse is false, then
    +     * the result is the number of children whose status has failed
    +     * (so a failed status with some passing and no failing children
    +     *  will return 0).
    +     * If it has children and recurse is true,
    +     * then return the number of leaf failures in the tree,
    +     * ignoring (roll-up) node failures.
    +     * @return number of failures in children of this status 
    +     */
    +    public static int numFailures(IRunStatus status, boolean recurse) {
    +        int numFails = 0;
    +        IRunStatus[] children = status.getChildren();
    +        int numChildren = (null == children? 0 : children.length);
    +        if (0 == numChildren) {
    +            if (!RunValidator.NORMAL.runPassed(status)) {
    +                return 1;
    +            }
    +        } else { 
    +            int i = 0;
    +            for (int j = 0; j < children.length; j++) {
    +                if (recurse) {
    +                    numFails += numFailures(children[j], recurse);
    +                } else {
    +                    if (!RunValidator.NORMAL.runPassed(children[j])) {
    +                        numFails++;
    +                    }
    +                }
    +            }
    +        }
    +        return numFails;
    +    }
    +    
    +    // ------------------------ printing status
    +    public static void printShort(PrintStream out, IRunStatus status) {
    +        if ((null == out) || (null == status)) {
    +            return;
    +        }
    +        printShort(out, "", status);
    +    }
    +
    +    public static void printShort(PrintStream out, String prefix, IRunStatus status) {
    +        int numFails = numFailures(status, true);
    +        String fails = (0 == numFails ? "" : " - " + numFails + " failures");
    +        out.println(prefix + toShortString(status) + fails);
    +        IRunStatus[] children = status.getChildren();
    +        int numChildren = (null == children? 0 : children.length);
    +        if (0 < numChildren) {
    +            int i = 0;
    +            for (int j = 0; j < children.length; j++) {
    +                printShort(out, prefix + "[" + LangUtil.toSizedString(i++, 3) + "]: ", children[j]);
    +                if (!RunValidator.NORMAL.runPassed(children[j])) {
    +                    numFails++;
    +                }
    +            }
    +        }
    +    }
    +
    +    public static void print(PrintStream out, IRunStatus status) {
    +        if ((null == out) || (null == status)) {
    +            return;
    +        }
    +        print(out, "", status);
    +    }
    +    
    +    public static void print(PrintStream out, String prefix, IRunStatus status) {
    +        print(out, prefix, status, MessageUtil.MESSAGE_ALL, MessageUtil.PICK_ALL);
    +    }     
    +
    +    public static void print(PrintStream out, String prefix, IRunStatus status,
    +        IMessageRenderer renderer, IMessageHandler selector) {
    +        String label = status.getIdentifier()         
    +                    + (status.runResult() ? "PASS" : "FAIL");
    +        out.println(prefix + label);
    +        out.println(prefix + debugString(status));
    +        IMessageHolder messageHolder = status;
    +        if ((null != messageHolder) && (0 < messageHolder.numMessages(null, true))) {
    +            MessageUtil.print(out, messageHolder, prefix, renderer, selector);
    +        }   
    +        Throwable thrown = status.getThrown();
    +        if (null != thrown) {
    +            out.println(prefix + "--- printing stack trace for thrown");
    +            thrown.printStackTrace(out);
    +        } 
    +        IRunStatus[] children = status.getChildren();
    +        int numChildren = (null == children? 0 : children.length);
    +        int numFails = 0;
    +        if (0 < numChildren) {
    +            out.println(prefix + "--- printing children [" + numChildren + "]");
    +            int i = 0;
    +            for (int j = 0; j < children.length; j++) {
    +                print(out, prefix + "[" + LangUtil.toSizedString(i++, 3) + "]: ", children[j]);
    +                if (!RunValidator.NORMAL.runPassed(children[j])) {
    +                    numFails++;
    +                }
    +            }
    +        }
    +        if (0 < numFails) {
    +            label = numFails + " fails " + label;
    +        }
    +        out.println("");
    +    }
    +
    +   
    +    public static String debugString(IRunStatus status) {
    +        if (null == status) {
    +            return "null";
    +        }
    +        final String[] LABELS =
    +            new String[] {
    +                "runResult",
    +                "id",
    +                "result",
    +                "numChildren",
    +                "completed",
    +                //"parent",
    +                "abort",
    +                "started",
    +                "thrown",
    +                "messages" };
    +        String runResult = status.runResult() ? "PASS" : "FAIL";
    +        Throwable thrown = status.getThrown();
    +        String thrownString = LangUtil.unqualifiedClassName(thrown);
    +        IRunStatus[] children = status.getChildren();
    +        String numChildren = (null == children? "0" : ""+children.length);
    +        String numMessages = ""+status.numMessages(null, IMessageHolder.EQUAL);
    +        Object[] values =
    +            new Object[] {
    +                runResult,
    +                status.getIdentifier(),
    +                status.getResult(),
    +                numChildren,
    +                new Boolean(status.isCompleted()),
    +                //status.getParent(),               // costly if parent printing us
    +                status.getAbortRequest(),
    +                new Boolean(status.started()),
    +                thrownString,
    +                numMessages };
    +        return org.aspectj.testing.util.LangUtil.debugStr(status.getClass(), LABELS, values);
    +    }
    +
    +    public static String toShortString(IRunStatus status) {
    +        if (null == status) {
    +            return "null";
    +        }
    +        String runResult = status.runResult() ? " PASS: " : " FAIL: ";
    +        return (runResult + status.getIdentifier());
    +    }
    +    
    +    /** renderer for IRunStatus */
    +    public static interface IRunStatusPrinter {
    +        void printRunStatus(PrintStream out, IRunStatus status);
    +    }
    +    
    +    public static final IRunStatusPrinter VERBOSE_PRINTER = new IRunStatusPrinter() {
    +        public String toString() { return "VERBOSE_PRINTER"; }
    +        /** Render IRunStatus produced by running an AjcTest */
    +        public void printRunStatus(PrintStream out, IRunStatus status) {
    +            printRunStatus(out, status, "");
    +        }
    +        private void printRunStatus(PrintStream out, IRunStatus status, String prefix) {
    +            LangUtil.throwIaxIfNull(out, "out");
    +            LangUtil.throwIaxIfNull(status, "status");
    +            String label = (status.runResult() ? " PASS: " : " FAIL: ") 
    +                + status.getIdentifier();       
    +            out.println(prefix + "------------ " + label);
    +            out.println(prefix + "--- result: " + status.getResult());
    +            if (0 < status.numMessages(null, true)) {
    +                out.println(prefix + "--- messages ");
    +                MessageUtil.print(out, status, prefix, MessageUtil.MESSAGE_ALL, MessageUtil.PICK_ALL);
    +            }
    +            Throwable thrown = status.getThrown();
    +            if (null != thrown) {
    +                out.println(prefix + "--- thrown");
    +                thrown.printStackTrace(out);
    +            } 
    +            IRunStatus[] children = status.getChildren();
    +            for (int i = 0; i < children.length; i++) {
    +                String number = "[" + LangUtil.toSizedString(i,3) + "] ";
    +                printRunStatus(out, children[i], prefix + number);
    +			}
    +        }            
    +    };
    +
    +    /** print only status and fail/abort messages */
    +    public static final IRunStatusPrinter TERSE_PRINTER = new IRunStatusPrinter() {
    +        public String toString() { return "TERSE_PRINTER"; }
    +
    +        /** print only status and fail messages */
    +        public void printRunStatus(PrintStream out, IRunStatus status) {
    +            printRunStatus(out, status, "");
    +        }
    +        private void printRunStatus(PrintStream out, IRunStatus status, String prefix) {
    +            LangUtil.throwIaxIfNull(out, "out");
    +            LangUtil.throwIaxIfNull(status, "status");
    +            String label = (status.runResult() ? "PASS: " : "FAIL: ") 
    +                + status.getIdentifier();       
    +            out.println(prefix + label);
    +            Object result = status.getResult();
    +            if ((null != result) && (IRunStatus.PASS != result) && (IRunStatus.FAIL != result)) {
    +                out.println(prefix + "--- result: " + status.getResult());
    +            } 
    +            if (0 < status.numMessages(IMessage.FAIL, true)) {
    +                MessageUtil.print(out, status, prefix, MessageUtil.MESSAGE_ALL, MessageUtil.PICK_FAIL_PLUS);
    +            }
    +            Throwable thrown = status.getThrown();
    +            if (null != thrown) {
    +                out.println(prefix + "--- thrown: " + LangUtil.renderException(thrown, true));
    +            } 
    +            IRunStatus[] children = status.getChildren();
    +            for (int i = 0; i < children.length; i++) {
    +                if (!children[i].runResult()) {
    +                    String number = "[" + LangUtil.toSizedString(i,3) + "] ";
    +                    printRunStatus(out, children[i], prefix + number);
    +                }
    +            }
    +            out.println("");
    +        }
    +    };
    +
    +    /** Render IRunStatus produced by running an AjcTest.Suite. */
    +    public static final IRunStatusPrinter AJCSUITE_PRINTER  = new IRunStatusPrinter() {
    +        public String toString() { return "AJCSUITE_PRINTER"; }
    +        
    +        /** 
    +         * Render IRunStatus produced by running an AjcTest.Suite.
    +         * This renders only test failures and 
    +         * a summary at the end.
    +         */
    +        public void printRunStatus(PrintStream out, IRunStatus status) {
    +            LangUtil.throwIaxIfNull(out, "out");
    +            LangUtil.throwIaxIfNull(status, "status");
    +            final String prefix = "";
    +            final boolean failed = status.runResult();
    +            String label = (status.runResult() ? "PASS: " : "FAIL: ") 
    +                + status.getIdentifier();       
    +            out.println(prefix + label);
    +            // print all messages - these are validator comments
    +            if (0 < status.numMessages(null, true)) {
    +                MessageUtil.print(out, status, "init", MessageUtil.MESSAGE_ALL, MessageUtil.PICK_ALL);
    +            }
    +            // XXX ignore thrown if failed - will be printed as message anyway?
    +            Throwable thrown = status.getThrown();
    +            if ((null != thrown) && !failed) {
    +                out.println(prefix + "--- printing stack trace for thrown");
    +                thrown.printStackTrace(out);
    +            } 
    +            IRunStatus[] children = status.getChildren();
    +            int numChildren = (null == children? 0 : children.length);
    +            int numFails = 0;
    +            if (0 < numChildren) {
    +                for (int j = 0; j < children.length; j++) {
    +                    if (!RunValidator.NORMAL.runPassed(children[j])) {
    +                        numFails++;
    +                    }
    +                }
    +            }
    +            if (0 < numFails) {
    +                out.println(prefix + "--- " + numFails + " failures when running " + children.length + " tests");
    +                for (int j = 0; j < children.length; j++) {
    +                    if (!RunValidator.NORMAL.runPassed(children[j])) {
    +                        print(out, prefix + "[" + LangUtil.toSizedString(j, 3) + "]: ", children[j]);
    +                        out.println("");
    +                    }
    +                }
    +            }
    +            label = "ran " + children.length + " tests" 
    +                + (numFails == 0 ? "" : "(" + numFails + " fails)");            
    +            out.println("");
    +        }
    +        
    +    };
    +    /** Render IRunStatus produced by running an AjcTest (verbose) */
    +    public static final IRunStatusPrinter AJCTEST_PRINTER = VERBOSE_PRINTER;    
    +
    +	/** print this with messages, then children using AJCRUN_PRINTER */
    +    public static final IRunStatusPrinter AJC_PRINTER = new IRunStatusPrinter() {
    +        public String toString() { return "AJC_PRINTER"; }
    +        /** Render IRunStatus produced by running an AjcTest */
    +        public void printRunStatus(PrintStream out, IRunStatus status) {
    +            LangUtil.throwIaxIfNull(out, "out");
    +            LangUtil.throwIaxIfNull(status, "status");
    +            String label = (status.runResult() ? " PASS: " : " FAIL: ") 
    +                + status.getIdentifier();       
    +            out.println("------------ " + label);
    +            MessageUtil.print(out, status, "", MessageUtil.MESSAGE_ALL, MessageUtil.PICK_ALL);
    +            IRunStatus[] children = status.getChildren();
    +            for (int i = 0; i < children.length; i++) {
    +                AJCRUN_PRINTER.printRunStatus(out, children[i]);
    +			}
    +            //out.println("------------   END "  + label);
    +            out.println("");
    +        }        
    +    };
    +
    +
    +	/** print only fail messages */
    +    public static final IRunStatusPrinter AJCRUN_PRINTER = new IRunStatusPrinter() {
    +        public String toString() { return "AJCRUN_PRINTER"; }
    +        /** Render IRunStatus produced by running an AjcTest child */
    +        public void printRunStatus(PrintStream out, IRunStatus status) {
    +            LangUtil.throwIaxIfNull(out, "out");
    +            LangUtil.throwIaxIfNull(status, "status");
    +            final boolean orGreater = false;
    +            int numFails = status.numMessages(IMessage.FAIL, orGreater);
    +            if (0 < numFails) { 
    +                out.println("--- " + status.getIdentifier());
    +                IMessage[] fails = status.getMessages(IMessage.FAIL, orGreater); 
    +                for (int i = 0; i < fails.length; i++) {
    +                    out.println("[fail " + LangUtil.toSizedString(i, 3) + "]: " 
    +                      + MessageUtil.MESSAGE_ALL.renderToString(fails[i]));
    +				}
    +            }
    +        }        
    +    };
    +
    +	private RunUtils() {
    +	}
    +    
    +}
    diff --git a/testing/src/org/aspectj/testing/util/SFileReader.java b/testing/src/org/aspectj/testing/util/SFileReader.java
    new file mode 100644
    index 000000000..f2b57e13d
    --- /dev/null
    +++ b/testing/src/org/aspectj/testing/util/SFileReader.java
    @@ -0,0 +1,185 @@
    +/* *******************************************************************
    + * Copyright (c) 1999-2001 Xerox Corporation, 
    + *               2002 Palo Alto Research Center, Incorporated (PARC).
    + * All rights reserved. 
    + * This program and the accompanying materials are made available 
    + * under the terms of the Common Public License v1.0 
    + * which accompanies this distribution and is available at 
    + * http://www.eclipse.org/legal/cpl-v10.html 
    + *  
    + * Contributors: 
    + *     Xerox/PARC     initial implementation 
    + * ******************************************************************/
    +
    +package org.aspectj.testing.util;
    +
    +import org.aspectj.bridge.AbortException;
    +import org.aspectj.bridge.IMessage;
    +import org.aspectj.util.LangUtil;
    +import org.aspectj.util.LineReader;
    +
    +import java.io.File;
    +import java.io.IOException;
    +import java.io.PrintStream;
    +import java.util.ArrayList;
    +
    +
    +/**
    + * This reads a structured (config) file, which may have
    + * lines with @ signalling a recursive read
    + * and EOL comments # or //.
    + * This duplicates ConfigFileUtil in some sense.
    +  */
    +public class SFileReader { 
    +    // XXX move into LineReader, but that forces util to depend on AbortException?
    +    // Formerly in SuiteReader
    +    
    +    /** 
    +     * Read args as config files and echo to stderr.
    +     * @param args String[] of fully-qualified paths to config files
    +     */
    +    public static void main(String[] args) throws IOException {
    +        ArrayList result = new ArrayList();
    +        ObjectChecker collector = new StandardObjectChecker(String.class, result);
    +        SFileReader me = new SFileReader(null);
    +        for (int i = 0; i < args.length; i++) {
    +		  Node node = me.readNodes(new File(args[i]), null, true, System.err);	
    +            if (!Node.visit(node, collector, null)) {
    +                System.err.println("halted during copy of " +args[i]);
    +            } else {
    +              String s = org.aspectj.testing.util.LangUtil.debugStr(null, "\n  ", null,
    +                null, result.toArray(), "\n  ", "");
    +              System.err.println(args[i] + ": " + s);
    +            }
    +		}
    +    }
    +    
    +    /*
    +     * readSuite(..) reads .txt file, and for each test case specification
    +     * creates a spec using readTestSpecifications 
    +     * and (if the specifications match the constraints)
    +     * creates a test case using creatTestCase.
    +     */
    +
    +    /** pass this to protected methods requiring String[] if you have none */
    +    protected static final String[] NONE = new String[0];
    +    
    +    final Maker maker;
    +    
    +    /** @param maker the Maker factory to use - if null, use Maker.ECHO */
    +    public SFileReader(Maker maker) {
    +        this.maker = (null == maker ? Maker.ECHO : maker);
    +    }
    +
    +    /**
    +     * Creates a (potentially recursive) tree of node
    +     * by reading from the file and constructing using the maker.
    +     * Clients may read results in Node tree form when complete
    +     * or snoop the selector for a list of objects made.
    +     * The selector can prevent collection in the node by
    +     * returning false.
    +     * Results are guaranteed by the Maker to be of the Maker's type.
    +     * @param file an absolute path to a structured file
    +     * @param selector determines whether not to keep an object made.
    +     *         (if null, then all are kept)
    +     * @return Node with objects available from getItems()
    +     *          and sub-suite Node available from getNodes()
    +     * @throws Error on any read error if abortOnReadError (default)
    +     */
    +    public Node readNodes(
    +        final File file,
    +        final ObjectChecker selector,
    +        final boolean abortOnReadError,
    +        final PrintStream err)
    +        throws IOException {
    +        final Node result = new Node(file.getPath(), maker.getType());
    +        if (null == file) {
    +            throw new IllegalArgumentException("null file");
    +        } else if (!file.isAbsolute()) {
    +            throw new IllegalArgumentException("file not absolute");
    +        }
    +        LineReader reader = null;
    +        try {
    +            reader = LineReader.createTester(file);
    +            if (null == reader) {
    +                throw new IOException("no reader for " + file);
    +            }
    +            final String baseDir = file.getParent();
    +    
    +            String line;
    +            boolean skipEmpties = true;
    +            while (null != (line = reader.nextLine(skipEmpties))) {
    +                if (line.charAt(0) == '@') {
    +                    if (line.length() > 1) {
    +                        String newFilePath = line.substring(1).trim();
    +                        File newFile = new File(newFilePath);
    +                        if (!newFile.isAbsolute()) {
    +                            newFile = new File(baseDir, newFilePath);
    +                        }
    +                        Node node = readNodes(newFile, selector, abortOnReadError, err);
    +                        if (!result.addNode(node)) {
    +                            // XXX signal error?
    +                            System.err.println("warning: unable to add node: " + node);
    +                            break;
    +                        }
    +                    }
    +                } else {
    +                    try {
    +                        Object made = maker.make(reader);
    +                        if ((null == selector) || (selector.isValid(made))) {
    +                            if (!result.add(made)) { 
    +                               break;  // XXX signal error?
    +                            }
    +                        }
    +                    } catch (AbortException e) {
    +                        if (abortOnReadError) { // XXX todo - verify message has context?
    +                           throw e;
    +                        }
    +                        if (null != err) {
    +                            String m;
    +                            IMessage mssg = e.getIMessage();
    +                            if (null != mssg) {
    +                                m = "Message: " + mssg;
    +                            } else {
    +                                m = LangUtil.unqualifiedClassName(e) + "@" + e.getMessage();
    +                            }
    +                            err.println(m);
    +                        }
    +                        reader.readToBlankLine();
    +                    }
    +                } 
    +            }
    +        } finally {
    +            try {
    +                if (null != reader) {
    +                    reader.close();
    +                }
    +            } catch (IOException e) {
    +            } // ignore
    +        }
    +        
    +        return result;
    +    }
    +    
    +    /** factory produces objects by reading LineReader */
    +    public interface Maker {
    +        /** 
    +         * Make the result using the input from the LineReader,
    +         * starting with lastLine().
    +         */
    +        Object make(LineReader reader) throws AbortException, IOException;
    +        
    +        /** @return type of the Object made */
    +        Class getType();
    +        
    +        /** This echoes each line, prefixed by the reader.
    +         * @return file:line: {line}
    +         */
    +        static final Maker ECHO = new Maker() {
    +            public Object make(LineReader reader) {
    +                return reader + ": " + reader.lastLine();
    +            }
    +            public Class getType() { return String.class; }
    +        };
    +    }
    +}
    diff --git a/testing/src/org/aspectj/testing/util/StandardObjectChecker.java b/testing/src/org/aspectj/testing/util/StandardObjectChecker.java
    new file mode 100644
    index 000000000..ee5a466f5
    --- /dev/null
    +++ b/testing/src/org/aspectj/testing/util/StandardObjectChecker.java
    @@ -0,0 +1,127 @@
    +/* *******************************************************************
    + * Copyright (c) 1999-2001 Xerox Corporation, 
    + *               2002 Palo Alto Research Center, Incorporated (PARC).
    + * All rights reserved. 
    + * This program and the accompanying materials are made available 
    + * under the terms of the Common Public License v1.0 
    + * which accompanies this distribution and is available at 
    + * http://www.eclipse.org/legal/cpl-v10.html 
    + *  
    + * Contributors: 
    + *     Xerox/PARC     initial implementation 
    + * ******************************************************************/
    +
    +
    +/*
    + * StandardObjectChecker.java created on May 7, 2002
    + *
    + */
    +package org.aspectj.testing.util;
    +
    +import java.util.List;
    +
    +/**
    + * Superclass for checkers that require non-null input
    + * of a given type.
    + * Clients may supply delegator for further checks,
    + * or a list to collect results.
    + * Subclasses may instead implement doIsValid().
    + * @author isberg
    + */
    +public class StandardObjectChecker implements ObjectChecker {
    +
    +    public final Class type;
    +    private final ObjectChecker delegate;
    +    private final List collector;
    +    private final boolean collectionResult;
    +    
    +    /**
    +     * Create one with no delegate.
    +     * @param type the Class of the type required of the input
    +     */
    +    public StandardObjectChecker(Class type) {
    +        this(type, ANY, (List) null, true);
    +    }
    +    
    +    /**
    +     * @param type the Class of the type required of the input
    +     * @param delegate the ObjectChecker to delegate to after
    +     *         checking for non-null input of the correct type.
    +     */
    +    public StandardObjectChecker(Class type, ObjectChecker delegate) {
    +        this(type, delegate, null, true);
    +    }
    +
    +    /**
    +     * same as StandardObjectChecker(type, collector, true)
    +     * @param type the Class of the type required of the input
    +     * @param collector the list to collect valid entries
    +     */
    +    public StandardObjectChecker(Class type, List collector) {
    +        this(type, ANY, collector, true);
    +    }
    +    
    +    /**
    +     * @param type the Class of the type required of the input
    +     * @param collector the list to collect valid entries
    +     * @param collectionResult the value to return when entry was added
    +     */
    +    public StandardObjectChecker(Class type, List collector, boolean collectionResult) {
    +        this(type, ANY, collector, collectionResult);
    +    }
    +    
    +    /**
    +     * @param type the Class of the type required of the input
    +     * @param collector the list to collect valid entries
    +     */
    +    public StandardObjectChecker(Class type, ObjectChecker delegate, 
    +                                List collector, boolean collectionResult) {
    +        if (null == type) throw new IllegalArgumentException("null type");
    +        this.type = type;
    +        this.delegate = delegate;
    +        this.collector = collector;
    +        this.collectionResult = collectionResult;
    +    }
    +    
    +    /**
    +     * Check if object is valid by confirming is is non-null and of the
    +     * right type, then delegating to any delegate or calling doIsValid(),
    +     * then (if true) passing to any collector, and returning
    +     * false if the collector failed or the collection result otherwise.
    +     * @see ObjectChecker#isValid(Object)
    +     * @return true unless input is null or wrong type 
    +     *          or if one of subclass (doIsValid(..)) or delegates
    +     *          (list, collector) returns false.
    +     */
    +    public final boolean isValid(Object input) {
    +        if ((null == input) || (!(type.isAssignableFrom(input.getClass())))) {
    +            return false;
    +        } else if (null != delegate) {
    +            if (!delegate.isValid(input)) {
    +                return false;
    +            }
    +        } 
    +        if (!doIsValid(input)) {
    +            return false;
    +        }
    +        if (null == collector) {
    +            return true;
    +        } else if (!collector.add(input)) {
    +            return false;
    +        } else {
    +            return collectionResult;
    +        }
    +    }
    +    
    +    /**
    +     * Delegate of isValid guarantees that the input
    +     * is not null as is assignable to the specified type.
    +     * Subclasses implement their funtionality here.
    +     * This implementation returns true;
    +     * @return true
    +     */
    +    public boolean doIsValid(Object input) {
    +        return true;
    +    }
    +
    +}
    diff --git a/testing/src/org/aspectj/testing/util/StreamSniffer.java b/testing/src/org/aspectj/testing/util/StreamSniffer.java
    new file mode 100644
    index 000000000..8b740aa2b
    --- /dev/null
    +++ b/testing/src/org/aspectj/testing/util/StreamSniffer.java
    @@ -0,0 +1,76 @@
    +/* *******************************************************************
    + * Copyright (c) 1999-2001 Xerox Corporation, 
    + *               2002 Palo Alto Research Center, Incorporated (PARC).
    + * All rights reserved. 
    + * This program and the accompanying materials are made available 
    + * under the terms of the Common Public License v1.0 
    + * which accompanies this distribution and is available at 
    + * http://www.eclipse.org/legal/cpl-v10.html 
    + *  
    + * Contributors: 
    + *     Xerox/PARC     initial implementation 
    + * ******************************************************************/
    +
    +
    +/*
    + * StreamGrabber.java created on May 16, 2002
    + *
    + */
    +package org.aspectj.testing.util;
    +
    +import java.io.FilterOutputStream;
    +import java.io.IOException;
    +import java.io.OutputStream;
    +
    +/**
    +  * Listen to a stream using StringBuffer.
    +  * Clients install and remove buffer to enable/disable listening.
    +  * Does not affect data passed to underlying stream
    +  */
    +public class StreamSniffer extends FilterOutputStream {
    +    StringBuffer buffer;
    +    /** have to use delegate, not super, because super we will double-count input */
    +    final OutputStream delegate;
    +    
    +    public StreamSniffer(OutputStream stream) {
    +        super(stream);
    +        delegate = stream;
    +    }
    +
    +    /** set to null to stop copying */
    +    public void setBuffer(StringBuffer sb) {
    +        buffer = sb;
    +    }
    +
    +    //---------------- FilterOutputStream 
    +    public void write(int b) throws IOException {
    +        StringBuffer sb = buffer;
    +        if (null != sb) {
    +            if ((b > Character.MAX_VALUE) 
    +                || (b < Character.MIN_VALUE)) {
    +                throw new Error("don't know double-byte"); // XXX
    +            } else {
    +                sb.append((char) b);
    +            }
    +        }
    +        delegate.write(b);
    +    }
    +    
    +    public void write(byte[] b) throws IOException {
    +        StringBuffer sb = buffer;
    +        if (null != sb) {
    +            String s = new String(b);
    +            sb.append(s);
    +        }
    +        delegate.write(b);
    +    }
    +    
    +    public void write(byte[] b, int offset, int length) throws IOException {
    +        StringBuffer sb = buffer;
    +        if (null != sb) {
    +            String s = new String(b, offset, length);
    +            sb.append(s);
    +        }
    +        delegate.write(b, offset, length);
    +    }
    +}
    diff --git a/testing/src/org/aspectj/testing/util/StreamsHandler.java b/testing/src/org/aspectj/testing/util/StreamsHandler.java
    new file mode 100644
    index 000000000..bd550ada7
    --- /dev/null
    +++ b/testing/src/org/aspectj/testing/util/StreamsHandler.java
    @@ -0,0 +1,217 @@
    +/* *******************************************************************
    + * Copyright (c) 1999-2001 Xerox Corporation, 
    + *               2002 Palo Alto Research Center, Incorporated (PARC).
    + * All rights reserved. 
    + * This program and the accompanying materials are made available 
    + * under the terms of the Common Public License v1.0 
    + * which accompanies this distribution and is available at 
    + * http://www.eclipse.org/legal/cpl-v10.html 
    + *  
    + * Contributors: 
    + *     Xerox/PARC     initial implementation 
    + * ******************************************************************/
    +
    +package org.aspectj.testing.util;
    +
    +import java.io.PrintStream;
    +
    +/**
    + * Manage system err and system out streams.
    + * Clients can suppress stream output during StreamsHandler lifecycle
    + * and intermittantly listen to both streams if signalled on construction.
    + * To print to the underlying streams (without hiding or listening),
    + * use either the log methods (which manage lineation)
    + * or the out and err fields.
    + * 
    + * boolean hideStreams = true;
    + * boolean listen = true;
    + * StreamsHander streams = new StreamsHander(hideStreams, listen);
    + * streams.startListening();
    + * ...
    + * streams.out.println("this goes out to without listening"); XXX verify
    + * StreamsHandler.Result result = streams.stopListening();
    + * streams.restoreStreams();
    + * System.out.println("Suppressed output stream follows");
    + * System.out.print(result.out);
    + * System.out.println("Suppressed error stream follows");
    + * System.out.print(result.err);
    + * 
    + * Warning: does not distinguish streams from different threads. + */ +public class StreamsHandler { + + /** real output stream and sink for log if logToOut */ + public final PrintStream out; + + /** real error stream and sink for log if !logToOut */ + public final PrintStream err; + + /** if true, then can listen using startListening() */ + protected final boolean listening; + + /** if logToOut, then out, else err */ + private final PrintStream log; + + /** true if the last logged item was a newline */ + private boolean loggedLine; + + /** sniffs stream to gather test output to System.out */ + protected StreamSniffer outSniffer; + + /** sniffs stream to gather test output to System.err */ + protected StreamSniffer errSniffer; + + /** permits us to hide output stream (after sniffing by outSniffer */ + protected ProxyPrintStream outDelegate; + + /** permits us to hide error stream (after sniffing by errSniffer */ + protected ProxyPrintStream errDelegate; + + /** when sniffing, this has sniffed contents of output stream */ + protected StringBuffer outListener; + + /** when sniffing, this has sniffed contents of error stream */ + protected StringBuffer errListener; + + /** @param hide if true, then suppress stream output (can still listen) */ + public StreamsHandler(boolean listen) { + this(listen, false); + } + + /** + * @param listen possible to sniff streams only if true + * @param logToOut if true, then log methods go to System.out -- otherwise, System.err. + */ + public StreamsHandler( + boolean listen, + boolean logToOut) { + this.err = System.err; + this.out = System.out; + outDelegate = new ProxyPrintStream(System.out); + errDelegate = new ProxyPrintStream(System.err); + this.listening = listen; + final PrintStream HIDE = NullPrintStream.NULL_PrintStream; + outSniffer = new StreamSniffer(outDelegate); + System.setOut(new PrintStream(outSniffer)); + errSniffer = new StreamSniffer(errDelegate); + System.setErr(new PrintStream(errSniffer)); + log = (logToOut ? this.out : this.err); + loggedLine = true; + } + + /** render output and error streams (after sniffing) */ + public void show() { + outDelegate.show(); + errDelegate.show(); + } + + /** suppress output and error streams (after sniffing) */ + public void hide() { + outDelegate.hide(); + errDelegate.hide(); + } + + /** restore streams. Do not use this after restoring. */ + public void restoreStreams() { + if (null != outSniffer) { + outSniffer = null; + errSniffer = null; + System.setOut(out); + System.setErr(err); + } + } + + /** @return PrintStream used for direct logging */ + public PrintStream getLogStream() { + return log; + } + + /** log item without newline. */ + public void log(String s) { + log.print(s); + if (loggedLine) { + loggedLine = false; + } + } + + /** + * Log item with newline. + * If previous log did not have a newline, + * then this prepends a newline. + */ + public void lnlog(String s) { + if (!loggedLine) { + log.println(""); + } + log.println(s); + } + + /** + * Start listening to both streams. + * Tosses any old data captured. + * (Has no effect if not listening.) + * @throws IllegalStateException if called after restoreStreams() + * @see endListening() + */ + public void startListening() { + if (null == outSniffer) { + throw new IllegalStateException("no listening after restore"); + } + if (listening) { + if (null != outListener) { + outListener.setLength(0); + errListener.setLength(0); + } else { + outListener = new StringBuffer(); + outSniffer.setBuffer(outListener); + errListener = new StringBuffer(); + errSniffer.setBuffer(errListener); + } + } + } + + /** + * End listening to both streams and return data captured. + * Must call startListening() first. + * @throws IllegalStateException if called when not listening + * @return Result with sniffed output and error String + * @see startListening() + */ + public Result endListening() { + return endListening(true); + } + + /** + * End listening to both streams and return data captured. + * Must call startListening() first. + * @param getResult if false, return Result.EMPTY + * and avoid converting buffer to String. + * @throws IllegalStateException if called when not listening + * @return Result with sniffed output and error String + * @see startListening() + */ + public Result endListening(boolean getResult) { + if (!listening) { + return Result.EMPTY; + } + if (null == outListener) { + throw new IllegalStateException("listening not started"); + } + Result result = (!getResult ? Result.EMPTY + : new Result(outListener.toString(), errListener.toString())); + errListener = null; + outListener = null; + return result; + } + + /** output and error String */ + public static class Result { + static final Result EMPTY = new Result(null, null); + public final String out; + public final String err; + private Result(String out, String err) { + this.out = out; + this.err = err; + } + } +} diff --git a/testing/src/org/aspectj/testing/util/StringAccumulator.java b/testing/src/org/aspectj/testing/util/StringAccumulator.java new file mode 100644 index 000000000..ecff4fabc --- /dev/null +++ b/testing/src/org/aspectj/testing/util/StringAccumulator.java @@ -0,0 +1,99 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +/* + * StringAccumulator.java created on May 14, 2002 + * + */ +package org.aspectj.testing.util; + + + +/** + * Accumulate String with delimiters. + */ +public class StringAccumulator implements ObjectChecker { + + private final String prefix; + private final String infix; + private final String suffix; + private final String nullString; + private final StringBuffer sb; + private int index; + + /** + * Accumulate string with delimiter between elements, + * treaing null elements as "". + */ + public StringAccumulator(String delimiter) { + this(delimiter, null, null, ""); + } + + /** + * Constructor for StringAccumulator which specifies how to + * process each result, optionally postfixing or prefixing + * or infixing (adding index plus infix to prefix). e.g., + * for prefix="[", infix="]\"", postfix="\"\n", then each entry + * becomes a line:
    "[{index}]"{entry}"\n
    + * + * @param prefix if not null, prepend to each result + * @param infix if not null, the add index and infix before each result, after prefix + * @param postfix if not null, append to each result + * @param nullString if null, ignore null completely (no index); otherwise render null as nullString + * @param type + */ + public StringAccumulator(String prefix, String infix, String suffix, String nullString) { + this.prefix = prefix; + this.infix = infix; + this.suffix = suffix; + this.nullString = nullString; + sb = new StringBuffer(); + } + + /** Clear buffer and index */ + public synchronized void clear() { + sb.setLength(0); + index = 0; + } + + /** + * Accumulate input.toString into + * @return true + * @see StandardObjectChecker#doIsValid(Object) + */ + public synchronized boolean isValid(Object input) { + if (input == null) { + if (nullString == null) return true; // ignore + input = nullString; + } + if (null != prefix) sb.append(prefix); + if (null != infix) { + sb.append(index++ + infix); + } + sb.append(input.toString()); + if (null != suffix) sb.append(suffix); + return true; + } + + /** @return result accumulated so far */ + public String toString() { + return sb.toString(); + } + /** @return result accumulated so far */ + public String debugString() { + return "StringAccumulator prefix=" + prefix + " infix=" + infix + " suffix=" + suffix + + " nullString=" + nullString + " index=" + index + " toString=" + toString(); + } + +} diff --git a/testing/src/org/aspectj/testing/util/StringVisitor.java b/testing/src/org/aspectj/testing/util/StringVisitor.java new file mode 100644 index 000000000..3d14cb3f6 --- /dev/null +++ b/testing/src/org/aspectj/testing/util/StringVisitor.java @@ -0,0 +1,28 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + +// todo: non-distribution license? + +package org.aspectj.testing.util; + +/** + * Visitor interface for String +*/ +public interface StringVisitor { + /** + * @param input the String to evaluate - may be null + * @return true if input is accepted and/or process should continue + */ + public boolean accept(String input); +} + diff --git a/testing/src/org/aspectj/testing/util/TestClassLoader.java b/testing/src/org/aspectj/testing/util/TestClassLoader.java new file mode 100644 index 000000000..2af325e32 --- /dev/null +++ b/testing/src/org/aspectj/testing/util/TestClassLoader.java @@ -0,0 +1,154 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.util; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +/** + * Load classes as File from File[] dirs or URL[] jars. + */ +public class TestClassLoader extends URLClassLoader { + + List /*File*/ dirs; + + public TestClassLoader(URL[] urls, File[] dirs) { + super(urls); + if (null == dirs) { + throw new IllegalArgumentException("null dir"); + } + for (int i = 0; i < dirs.length; i++) { + if (null == dirs[i]) { + throw new IllegalArgumentException("null dir[" + i + "]"); + } + } + ArrayList dcopy = new ArrayList(); + + if ((null != dirs) && (0 < dirs.length)) { + dcopy.addAll(Arrays.asList(dirs)); + } + this.dirs = Collections.unmodifiableList(dcopy); + } + + + public URL getResource(String name) { + return ClassLoader.getSystemResource(name); + } + + public InputStream getResourceAsStream(String name) { + return ClassLoader.getSystemResourceAsStream(name); + } + + /** We don't expect test classes to have prefixes java, org, or com */ + protected boolean maybeTestClassName(String name) { + return (null != name) + && !name.startsWith("java") + && !name.startsWith("org.") + && !name.startsWith("com."); + } + + public synchronized Class loadClass(String name, boolean resolve) + throws ClassNotFoundException { + // search the cache, our dirs (if maybe test), + // the system, the superclass (URL[]), + // and our dirs again (if not maybe test) + ClassNotFoundException thrown = null; + final boolean maybeTestClass = maybeTestClassName(name); + + Class result = findLoadedClass(name); + if (null != result) { + resolve = false; + } else if (maybeTestClass) { + // subvert the dominant paradigm... + byte[] data = readClass(name); + if (data != null) { + result = defineClass(name, data, 0, data.length); + } // handle ClassFormatError? + } + if (null == result) { + try { + result = findSystemClass(name); + } catch (ClassNotFoundException e) { + thrown = e; + } + } + if (null == result) { + try { + result = super.loadClass(name, resolve); + } catch (ClassNotFoundException e) { + thrown = e; + } + if (null != result) { // resolved by superclass + return result; + } + } + if ((null == result) && !maybeTestClass) { + byte[] data = readClass(name); + if (data != null) { + result = defineClass(name, data, 0, data.length); + } // handle ClassFormatError? + } + + if (null == result) { + throw (null != thrown ? thrown : new ClassNotFoundException(name)); + } + if (resolve) { + resolveClass(result); + } + return result; + } + + private byte[] readClass(String className) throws ClassNotFoundException { + byte[] data= null; + final String fileName = className.replace('.', '/')+".class"; + for (Iterator iter = dirs.iterator(); iter.hasNext();) { + File file = new File((File) iter.next(), fileName); + if (file.canRead()) { + return getClassData(file); + } + if (data != null) { + return data; + } + } + throw new ClassNotFoundException(className); // expensive - fix? + } + + private byte[] getClassData(File f) { + try { + FileInputStream stream= new FileInputStream(f); + ByteArrayOutputStream out= new ByteArrayOutputStream(1000); + byte[] b= new byte[4096]; + int n; + while ((n= stream.read(b)) != -1) { + out.write(b, 0, n); + } + stream.close(); + out.close(); + return out.toByteArray(); + } catch (IOException e) { + } + return null; + } +} + diff --git a/testing/src/org/aspectj/testing/util/TestDiffs.java b/testing/src/org/aspectj/testing/util/TestDiffs.java new file mode 100644 index 000000000..2a847f039 --- /dev/null +++ b/testing/src/org/aspectj/testing/util/TestDiffs.java @@ -0,0 +1,360 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.util; + +import org.aspectj.util.LangUtil; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +/** + * Calculated differences between two test runs + * based on their output files + * assuming that tests are logged with prefix [PASS|FAIL] + * (as they are when using -traceTestsMin with the Harness). + * @see org.aspectj.testing.drivers.Harness + */ +public class TestDiffs { // XXX pretty dumb implementation + + /** @param args expected, actual test result files */ + public static void main(String[] args) { + if ((null == args) || (2 > args.length)) { + System.err.println("java " + TestDiffs.class.getName() + " expectedFile actualFile {test}"); + return; + } + File expected = new File(args[0]); + File actual = new File(args[1]); + + TestDiffs result = compareResults(expected, actual); + + System.out.println("## Differences between test runs"); + print(System.out, result.added, "added"); + print(System.out, result.missing, "missing"); + print(System.out, result.fixed, "fixed"); + print(System.out, result.broken, "broken"); + + System.out.println("## Summary"); + System.out.println(" # expected " + result.expected.size() + " tests: " + args[0] ); + System.out.println(" # actual " + result.actual.size() + " tests: " + args[1]); + StringBuffer sb = new StringBuffer(); + append(sb, result.added, " added"); + append(sb, result.missing, " missing"); + append(sb, result.broken, " broken"); + append(sb, result.fixed, " fixed"); + append(sb, result.stillPassing, " still passing"); + append(sb, result.stillFailing, " still failing"); + System.out.println(" # diffs: " + sb); + } + + /** + * @param expected the expected/old File results with Harness -traceTestsMin lines + * @param actual the actual/new File results with Harness -traceTestsMin lines + * @return TestDiffs null if error, valid otherwise + */ + public static TestDiffs compareResults(File expected, File actual) { + ArrayList exp = null; + ArrayList act = null; + File reading = expected; + try { + exp = TestDiffs.readTestResults(expected, expected.getPath()); + reading = actual; + act = TestDiffs.readTestResults(actual, actual.getPath()); + + Diffs tests = new Diffs("tests", exp, act, TestResult.BY_NAME); + // remove missing/unexpected (removed, added) tests from results + // otherwise, unexpected-[pass|fail] look like [fixes|broken] + ArrayList expResults = trimByName(exp, tests.missing); + ArrayList actResults = trimByName(act, tests.unexpected); + + Diffs results = new Diffs("results", expResults, actResults, TestResult.BY_PASSNAME); + + // broken tests show up in results as unexpected-fail or missing-pass + // fixed tests show up in results as unexpected-pass or missing-fail + ArrayList broken = new ArrayList(); + ArrayList fixed = new ArrayList(); + split(results.unexpected, fixed, broken); + + return new TestDiffs( + exp, + act, + tests.missing, + tests.unexpected, + broken, + fixed); + } catch (IOException e) { + System.err.println("error reading " + reading); + e.printStackTrace(System.err); // XXX + return null; + } + } + + private static void append(StringBuffer sb, List list, String label) { + if (!LangUtil.isEmpty(list)) { + if (0 < sb.length()) { + sb.append(" "); + } + sb.append(list.size() + label); + } + } + + private static void print(PrintStream out, List list, String label) { + if ((null == out) || LangUtil.isEmpty(list)) { + return; + } + int i = 0; + final String suffix = " " + label; + final String LABEL = list.size() + suffix; + out.println("## START " + LABEL); + for (Iterator iter = list.iterator(); iter.hasNext();) { + TestResult result = (TestResult) iter.next(); + out.println(" " + result.test + " ## " + suffix); + } + out.println("## END " + LABEL); + } + + /** + * Create ArrayList with input TestResult list + * but without elements in trim list, + * comparing based on test name only. + * @param input + * @param trim + * @return ArrayList with all input except those in trim (by name) + */ + private static ArrayList trimByName(List input, List trim) { + ArrayList result = new ArrayList(); + result.addAll(input); + if (!LangUtil.isEmpty(input) && !LangUtil.isEmpty(trim)) { + for (ListIterator iter = result.listIterator(); iter.hasNext();) { + TestResult inputItem = (TestResult) iter.next(); + for (Iterator iterator = trim.iterator(); + iterator.hasNext(); + ) { + TestResult trimItem = (TestResult) iterator.next(); + if (inputItem.test.equals(trimItem.test)) { + iter.remove(); + break; + } + } + } + } + return result; + } + + + /** split input List by whether the TestResult element passed or failed */ + private static void split(List input, ArrayList pass, ArrayList fail) { + for (ListIterator iter = input.listIterator(); iter.hasNext();) { + TestResult result = (TestResult) iter.next(); + if (result.pass) { + pass.add(result); + } else { + fail.add(result); + } + } + } + + /** + * Read a file of test results, + * defined as lines starting with [PASS|FAIL] + * (produced by Harness option -traceTestsmin). + * @return ArrayList of TestResult, one for every -traceTestsMin line in File + */ + private static ArrayList readTestResults(File file, String config) throws IOException { + LangUtil.throwIaxIfNull(file, "file"); + if (null == config) { + config = file.getPath(); + } + ArrayList result = new ArrayList(); + FileReader in = null; + try { + in = new FileReader(file); + BufferedReader input = new BufferedReader(in); + String line; + // XXX handle stream interleaving more carefully + // XXX clip trailing () + // XXX fix elision in test name rendering by -traceTestsMin? + while (null != (line = input.readLine())) { + boolean pass = line.startsWith("PASS"); + boolean fail = false; + if (!pass) { + fail = line.startsWith("FAIL"); + } + if (pass || fail) { + String test = line.substring(4).trim(); + result.add(new TestResult(test, config, pass)); + } + } + } finally { + if (null != in) { + try { in.close(); } + catch (IOException e) {} // ignore + } + } + return result; + } + + private static List safeList(List list) { + return (null == list + ? Collections.EMPTY_LIST + : Collections.unmodifiableList(list)); + } + + /** List of TestResult results from expected run. */ + public final List expected; + + /** List of TestResult results from actual run. */ + public final List actual; + + /** List of TestResult tests disappeared from test suite between expected and actual runs. */ + public final List missing; + + /** List of TestResult tests added to test suite between expected and actual runs. */ + public final List added; + + /** List of TestResult tests in both runs, expected to pass but actually failed */ + public final List broken; + + /** List of TestResult tests in both runs, expected to fail but actually passed */ + public final List fixed; + + /** List of TestResult passed tests in expected run */ + public final List expectedPassed; + + /** List of TestResult failed tests in expected run */ + public final List expectedFailed; + + /** List of TestResult passed tests in actual run */ + public final List actualPassed; + + /** List of TestResult tests failed in actual run */ + public final List actualFailed; + + /** List of TestResult tests passed in both expected and actual run */ + public final List stillPassing; + + /** List of TestResult tests failed in both expected and actual run */ + public final List stillFailing; + + private TestDiffs( + List expected, + List actual, + List missing, + List added, + List broken, + List fixed) { + this.expected = safeList(expected); + this.actual = safeList(actual); + this.missing = safeList(missing); + this.added = safeList(added); + this.broken = safeList(broken); + this.fixed = safeList(fixed); + // expected[Passed|Failed] + ArrayList passed = new ArrayList(); + ArrayList failed = new ArrayList(); + split(this.expected, passed, failed); + expectedPassed = safeList(passed); + expectedFailed = safeList(failed); + + // actual[Passed|Failed] + passed = new ArrayList(); + failed = new ArrayList(); + split(this.actual, passed, failed); + actualPassed = safeList(passed); + actualFailed = safeList(failed); + + // stillPassing: expected.passed w/o broken, missingPasses + passed = new ArrayList(); + passed.addAll(expectedPassed); + passed = trimByName(passed, this.broken); + ArrayList missingPasses = new ArrayList(); + ArrayList missingFails = new ArrayList(); + split(this.missing, missingPasses, missingFails); + passed = trimByName(passed, missingPasses); + stillPassing = safeList(passed); + + // stillFailing: expected.failed w/o fixed, missingFails + failed = new ArrayList(); + failed.addAll(expectedFailed); + failed = trimByName(failed, this.fixed); + failed = trimByName(failed, missingFails); + stillFailing = safeList(failed); + } + + /** results of a test */ + public static class TestResult { + public static final Comparator BY_PASSNAME = new Comparator() { + public int compare(Object o1, Object o2) { + if (o1 == o2) { + return 0; + } + TestResult lhs = (TestResult) o1; + TestResult rhs = (TestResult) o2; + return (lhs.pass == rhs.pass + ? lhs.test.compareTo(rhs.test) + : (lhs.pass ? 1 : -1 )); + } + + public boolean equals(Object lhs, Object rhs) { + return (0 == compare(lhs, rhs)); + } + }; + + public static final Comparator BY_NAME = new Comparator() { + public int compare(Object o1, Object o2) { + if (o1 == o2) { + return 0; + } + TestResult lhs = (TestResult) o1; + TestResult rhs = (TestResult) o2; + return lhs.test.compareTo(rhs.test); + } + + public boolean equals(Object lhs, Object rhs) { + return (0 == compare(lhs, rhs)); + } + }; + + //private static final ArrayList TESTS = new ArrayList(); + public static final String FIELDSEP = "\t"; + + public final String test; + public final String config; + public final boolean pass; + private final String toString; + + public TestResult(String test, String config, boolean pass) { + LangUtil.throwIaxIfNull(test, "test"); + LangUtil.throwIaxIfNull(test, "config"); + this.test = test; + this.config = config; + this.pass = pass; + toString = (pass ? "PASS" : "FAIL") + FIELDSEP + test + FIELDSEP + config; + + } + + /** @return [PASS|FAIL]{FIELDSEP}test{FIELDSEP}config */ + public String toString() { + return toString; + } + } +} diff --git a/testing/src/org/aspectj/testing/util/ValidFileFilter.java b/testing/src/org/aspectj/testing/util/ValidFileFilter.java new file mode 100644 index 000000000..ba9f74407 --- /dev/null +++ b/testing/src/org/aspectj/testing/util/ValidFileFilter.java @@ -0,0 +1,123 @@ +/* ******************************************************************* + * Copyright (c) 1999-2000 Xerox Corporation. + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.util; + +import java.io.File; +import java.io.FileFilter; + +/** + * FileFilter that accepts existing files + * with static singleton variants + * made from inner subclasses. + */ +public class ValidFileFilter implements FileFilter { + //----------------------------- singleton variants + public static final FileFilter EXIST = new ValidFileFilter(); + public static final FileFilter FILE_EXISTS = new FilesOnlyFilter(); + public static final FileFilter DIR_EXISTS = new DirsOnlyFilter(); + public static final FileFilter CLASS_FILE = new ClassOnlyFilter(); + public static final FileFilter JAVA_FILE = new JavaOnlyFilter(); + public static final FileFilter RESOURCE = new ResourcesOnlyFilter(); + + //----------------------------- members + protected final FileFilter delegate; + protected ValidFileFilter(){ + this(null); + } + protected ValidFileFilter(FileFilter delegate){ + this.delegate = delegate; + } + + /** + * Implement FileFilter.accept(File) by checking + * taht input is not null, exists, and is accepted by any delegate. + */ + public boolean accept(File f) { + return ((null != f) && (f.exists()) + && ((null == delegate) || delegate.accept(f))); + } + + //----------------------------- inner subclasses + static class FilesOnlyFilter extends ValidFileFilter { + public boolean accept(File f) { + return (super.accept(f) && (!f.isDirectory())); + } + } + static class ResourcesOnlyFilter extends FilesOnlyFilter { + public boolean accept(File f) { + return (super.accept(f) && (FileUtil.isResourcePath(f.getPath()))); + } + } + static class DirsOnlyFilter extends ValidFileFilter { + public final boolean accept(File f) { + return (super.accept(f) && (f.isDirectory())); + } + } + // todo: StringsFileFilter, accepts String[] variants for each + static class StringFileFilter extends ValidFileFilter { + public static final boolean IGNORE_CASE = true; + protected final String prefix; + protected final String substring; + protected final String suffix; + protected final boolean ignoreCase; + /** true if one of the String specifiers is not null */ + protected final boolean haveSpecifier; + public StringFileFilter(String prefix, String substring, + String suffix, boolean ignoreCase) { + this.ignoreCase = ignoreCase; + this.prefix = preprocess(prefix); + this.substring = preprocess(substring); + this.suffix = preprocess(suffix); + haveSpecifier = ((null != prefix) || (null != substring) + || (null != suffix)); + } + private final String preprocess(String input) { + if ((null != input) && ignoreCase) { + input = input.toLowerCase(); + } + return input; + } + public boolean accept(File f) { + if (!(super.accept(f))) { + return false; + } else if (haveSpecifier) { + String path = preprocess(f.getPath()); + if ((null == path) || (0 == path.length())) { + return false; + } + if ((null != prefix) && (!(path.startsWith(prefix)))) { + return false; + } + if ((null != substring) && (-1 == path.indexOf(substring))) { + return false; + } + if ((null != suffix) && (!(path.endsWith(suffix)))) { + return false; + } + } + return true; + } + } // class StringFileFilter + + static class ClassOnlyFilter extends StringFileFilter { + ClassOnlyFilter() { + super(null, null, ".class", IGNORE_CASE); + } + } + static class JavaOnlyFilter extends StringFileFilter { + JavaOnlyFilter() { + super(null, null, ".java", IGNORE_CASE); + } + } +} // class ValidFileFilter + diff --git a/testing/src/org/aspectj/testing/util/WebInstall.java b/testing/src/org/aspectj/testing/util/WebInstall.java new file mode 100644 index 000000000..4f8c96934 --- /dev/null +++ b/testing/src/org/aspectj/testing/util/WebInstall.java @@ -0,0 +1,208 @@ +/* ******************************************************************* + * Copyright (c) 1999-2000 Xerox Corporation. + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +package org.aspectj.testing.util; + +import java.io.File; +import java.io.FileWriter; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; + +/** + * Install programmatically using http URL. + * (Very strange that java tool classpath does not accept http URL's.) + * + * Example: + *

    java -classpath aj-testing.jar org.aspectj.testing.util.WebInstall + * http://aspectj.org/download/distribution/aspectj10-tools.jar -text + * install.properties + * + *

    You can omit the -text install.properties if there is + * a file called "install.properties" in the current directory. + * + *

    The properties file must define the properties + * output.dir and context.javaPath + * in properties- and platform specifie ways. + * For property values, a backslash must be escaped with another backslash, + * and directory separators should be valid. E.g., on Windows: + *

    output.dir=c:\\output\\dir
    + * context.javaPath=c:\\apps\\jdk1.3.1
    + * + * For an installer to complete programmatically, + * the output directory must be empty of colliding files. + * This will fail with a stack trace if anything goes wrong, except for + * simple input errors. + * + *

    You may also use this as a driver for the known installers + * by specifying the following options (-baseurl must be first):

    + *         -baseurl           {baseurl}
    + *         -version           {version}
    + *         -context.javaPath  {path to JDK}    (properties form)
    + *         -output.dir        {path to outDir} (properties form, including trailing /)
    + *         -outdir            {path to outDir} (actual form) 
    + * such that URL= + * {baseurl}{version}.jar + * and paths to context.javaPath and output.dir are specified in + * properties-compliant format + * + * @see ant script test-product.xml for example of installing from files + * which can be driven from the command-line. + */ +public class WebInstall { + private static final String EOL = "\n"; // todo where is this defined? + public static final String SYNTAX + = "java WebInstall url {args}" + EOL + + " url - to installer" + EOL + + " args - normally -text install.properties" + EOL + + " (if n/a, use install.properties)" + EOL; + + /** default arguments assume file install.properties + * is in current directory */ + private static final String[] ARGS = new String[] + { "-text", "install.properties" }; + + /** @param args the String[] { "" {, "-text", "" } */ + public static void main(String[] args) throws Exception { + if ((null != args) && (args.length > 0) + && ("-baseurl".equals(args[0]))) { + driver(args); + } else { + try { + new WebInstall().install(args); + } catch (Throwable t) { + System.err.println("Error installing args "); + for (int i = 0; i < args.length; i++) { + System.err.println(" " + i + ": " + args[i]); + } + t.printStackTrace(System.err); + } + } + } + + /** known .jar packages {(prefix, suffix}...} */ + protected static String[] packages = new String[] + { "aspectj-tools-", "" + , "aspectj-docs-", "" + , "ajde-forteModule-", "" + , "ajde-jbuilderOpenTool-", "" + }; + + /** + * Drive install of all jar-based installers. + * @param args the String[] containing
    +     *         -baseurl           {baseurl}
    +     *         -version           {version}
    +     *         -context.javaPath  {path to JDK}    (properties form)
    +     *         -output.dir        {path to outDir} (properties form, including trailing /)
    +     *         -outdir            {path to outDir} (actual form) 
    + * such that URL= + * {baseurl}{version}.jar + * and paths to context.javaPath and output.dir are specified in + * properties-compliant format + */ + protected static void driver(String[] args) throws Exception { + String baseurl = null; + String version = null; + String outputDir = null; + File outdir = null; + String jdk = null; + for (int i = 0; i < args.length; i++) { + if ("-baseurl".equals(args[i])) { + baseurl = args[++i]; + } else if ("-version".equals(args[i])) { + version = args[++i]; + } else if ("-context.javaPath".equals(args[i])) { + jdk = args[++i]; + } else if ("-output.dir".equals(args[i])) { + outputDir=args[++i]; + } else if ("-outdir".equals(args[i])) { + outdir = new File(args[++i]).getCanonicalFile(); + if (!outdir.isDirectory()) { + outdir.mkdir(); + } + } + } + final File props = File.createTempFile("WebInstall", null); + final String[] ARGS = new String [] {null, "-text", props.getCanonicalPath()}; + for (int i = 0; i < packages.length; i++) { + String name = packages[i++] + version + packages[i]; + File outDir = new File(outdir, name); + FileWriter fw = null; + try { + if (!outDir.isDirectory()) { + outDir.mkdir(); + } + fw = new FileWriter(props); + fw.write("output.dir=" + outputDir + name + "\n"); + fw.write("context.javaPath=" + jdk + "\n"); + fw.close(); + fw = null; + ARGS[0] = baseurl + name + ".jar"; + main(ARGS); + } finally { + try { if (null != fw) fw.close(); } + catch (java.io.IOException e) {} // ignore + } + } + if (props.exists()) props.delete(); + } // driver + + private static boolean printError(String err) { + if (null != err) System.err.println(err); + System.err.println(SYNTAX); + return (null != err); + } + + /** + * Create a classloader using the first argument (presumed to be URL for classloader), + * construct the installer, and invoke it using remaining arguments (or default args). + */ + protected void install(String[] args) throws Exception { + if ((null == args) || (args.length < 1) + || (null == args[0]) || (1 > args[0].length())) { + if (printError("expecting installer URL")) return; + } + URL[] urls = new URL[] { new URL(args[0]) }; + //System.err.println("before: " + render(args)); + args = getArgs(args); + //System.err.println("after: " + render(args)); + URLClassLoader cl = new URLClassLoader(urls); + Class c = cl.loadClass("$installer$.org.aspectj.Main"); // todo: dependency on class name + Method ms = c.getMethod("main", new Class[]{String[].class}); + ms.invoke(null, new Object[] { args }); + } + public static final String render(String[] args) { + StringBuffer sb = new StringBuffer(); + sb.append("["); + for (int i = 0; i < args.length; i++) { + if (0 < i) sb.append(", "); + sb.append("" + args[i]); + } + sb.append("]"); + return sb.toString(); + } + + /** @return args less args[0] or default args if less than 3 arguments */ + + protected String[] getArgs(String[] args) { + if ((null == args) || (args.length < 3)) { + return ARGS; + } else { + String[] result = new String[args.length-1]; + System.arraycopy(args, 1, result, 0, result.length); + return result; + } + } + +} diff --git a/testing/src/org/aspectj/testing/xml/AjcSpecXmlReader.java b/testing/src/org/aspectj/testing/xml/AjcSpecXmlReader.java new file mode 100644 index 000000000..cc484f82d --- /dev/null +++ b/testing/src/org/aspectj/testing/xml/AjcSpecXmlReader.java @@ -0,0 +1,432 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.xml; + +import org.apache.commons.digester.Digester; +import org.aspectj.bridge.AbortException; +import org.aspectj.bridge.IMessage; +import org.aspectj.bridge.ISourceLocation; +import org.aspectj.bridge.MessageUtil; +import org.aspectj.testing.harness.bridge.AbstractRunSpec; +import org.aspectj.testing.harness.bridge.AjcTest; +import org.aspectj.testing.harness.bridge.CompilerRun; +import org.aspectj.testing.harness.bridge.DirChanges; +import org.aspectj.testing.harness.bridge.IncCompilerRun; +import org.aspectj.testing.harness.bridge.JavaRun; +import org.aspectj.testing.util.RunUtils; +import org.aspectj.util.LangUtil; +import org.xml.sax.SAXException; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; + +/** + * Read an ajc test specification in xml form. + * Input files should comply with DOCTYPE + */ +public class AjcSpecXmlReader { + /* + * To add new elements or attributes: + * - update the DOCTYPE + * - update setupDigester(..) + * - new sub-elements should be created + * - new attributes should have values set as bean properties + * (possibly by mapping names) + * - new sub-elements should be added to parents + * => the parents need the add method - e.g., add{foo}({foo} toadd) + * - add tests + * - add compile-time checks for mapping APIs in + * setupDigesterComipileTimeCheck + * - when adding an attribute set by bean introspection, + * add to the list returned by expectedProperties() + * - update any client writers referring to the DOCTYPE, as necessary. + * - the parent IXmlWriter should delegate to the child component + * as IXmlWriter (or write the subelement itself) + */ + + private static final String EOL = "\n"; + + /** presumed relative-path to dtd file for any XML files written by writeSuiteToXmlFile */ + public static final String DTD_PATH = "../tests/ajcTestSuite.dtd"; + + /** expected doc type of AjcSpec XML files */ + public static final String DOCTYPE = ""; + + /** xml leader */ + public static final String FILE_LEADER + = ""; + + /** @return a String suitable as an inlined DOCTYPE statement */ + public static String inlineDocType() { + return ""; + } + + /** + * @return the elements of a document type as a String, + * using EOL as a line delimiter + */ + public static String getDocType() { + if (true) { + throw new Error("XXX using ajcTestSuite.dtd"); + } + StringBuffer r = new StringBuffer(); + final String suiteX = AjcTest.Suite.Spec.XMLNAME; + final String ajctestX = AjcTest.Spec.XMLNAME; + final String compileX = CompilerRun.Spec.XMLNAME; + final String inccompileX = IncCompilerRun.Spec.XMLNAME; + final String runX = JavaRun.Spec.XMLNAME; + final String dirchangesX = DirChanges.Spec.XMLNAME; + final String messageX = SoftMessage.XMLNAME; + + r.append(EOL + " "); + r.append(EOL + " "); + r.append(EOL + ""); + r.append(EOL + " "); + r.append(EOL + " "); + r.append(EOL + " "); + r.append(EOL + " "); + r.append(EOL + " "); + r.append(EOL + ""); + r.append(EOL + " "); // deprecate file? + r.append(EOL + " "); // if precursor to incremental + r.append(EOL + " "); + r.append(EOL + " "); + r.append(EOL + ""); + r.append(EOL + " "); // add file* if not deprecated + r.append(EOL + " "); + r.append(EOL + ""); + r.append(EOL + " "); + r.append(EOL + " "); + r.append(EOL + " "); + r.append(EOL + " "); + r.append(EOL + ""); + r.append(EOL + " "); // deprecate? + r.append(EOL + " "); + r.append(EOL + ""); + r.append(EOL + " "); + r.append(EOL + " "); + r.append(EOL + " "); + r.append(EOL + " "); // but Message requires non-null... + r.append(EOL + " "); + r.append(EOL + ""); + r.append(EOL + " "); + r.append(EOL + " "); + r.append(EOL + " "); + r.append(EOL + " "); + r.append(EOL + " "); + r.append(EOL + " "); + r.append(EOL + " "); + r.append(EOL + ""); + return r.toString(); + } + + private static final AjcSpecXmlReader ME + = new AjcSpecXmlReader(); + + /** @return shared instance */ + public static final AjcSpecXmlReader getReader() { + return ME; + } + + public static void main(String[] a) throws IOException { + writeDTD(new File("c:/home/wes/aj/aspectj/modules/tests/ajcTestSuite2.dtd")); + } + /** + * Write a DTD to dtdFile. + * @param dtdFile the File to write to + */ + public static void writeDTD(File dtdFile) throws IOException { + LangUtil.throwIaxIfNull(dtdFile, "dtdFile"); + PrintWriter out = new PrintWriter(new FileWriter(dtdFile)); + try { + out.println(""); + out.println(getDocType()); + } finally { + out.close(); + } + } + + private static final String[] LOG = new String[] {"info", "debug", "trace" }; + + private int logLevel; + + private AjcSpecXmlReader() {} + + /** @param level 0..2, info..trace */ + public void setLogLevel(int level) { + if (level < 0) { + level = 0; + } + if (level > 2) { + level = 2; + } + logLevel = level; + } + + /** + * Print an IXmlWritable to the output file + * with our leader and DOCTYPE. + * @param output the File to write to - overwritten + * @param tests the List of IXmlWritable to write + * @return null if no warnings detected, warnings otherwise + */ + public String writeSuiteToXmlFile(File output, IXmlWritable topNode) throws IOException { + PrintWriter writer = new PrintWriter(new FileOutputStream(output)); + XMLWriter printSink = new XMLWriter(writer); + writer.println(""); + writer.println(AjcSpecXmlReader.DOCTYPE); + writer.println(""); + topNode.writeXml(printSink); + writer.close(); + String parent = output.getParent(); + if (null == parent) { + parent = "."; + } + String dtdPath = parent + "/" + DTD_PATH; + File dtdFile = new File(dtdPath); + if (!dtdFile.canRead()) { + return "expecting dtd file: " + dtdFile.getPath(); + } + return null; + } + + + /** + * Read the specifications for a suite of AjcTest from an XML file. + * This also sets the suite dir in the specification. + * @param file the File must be readable, comply with DOCTYPE. + * @return AjcTest.Suite.Spec read from file + * @see setLogLevel(int) + */ + public AjcTest.Suite.Spec readAjcSuite(File file) throws IOException, AbortException { + // setup loggers for digester and beanutils... + System.setProperty("org.apache.commons.logging.Log", "org.apache.commons.logging.impl.SimpleLog"); // XXX + System.setProperty("org.apache.commons.logging.simplelog.defaultlog", LOG[logLevel]); // trace debug XXX + + final Digester digester = new Digester(); + setupDigester(digester); + + SuiteHolder holder = new SuiteHolder(); + digester.push(holder); + FileInputStream input = new FileInputStream(file); + try { + digester.parse(input); + } catch (SAXException e) { + MessageUtil.fail("parsing " + file, e); + } finally { + if (null != input) { + input.close(); + input = null; + } + } + AjcTest.Suite.Spec result = holder.spec; + if (null != result) { + File suiteDir = file.getParentFile(); + if (null == suiteDir) { + suiteDir = new File("."); // user.dir? + } + result.setSuiteDirFile(suiteDir); + if (result.runtime.isVerbose()) { // XXX hack fixup + RunUtils.enableVerbose(result); + } + } + return result; + } + + /** set up the mapping between the xml and Java. */ + private void setupDigester(Digester digester) { + // XXX supply sax parser to ignore white space? + digester.setValidating(true); + + // element names come from the element components + final String suiteX = AjcTest.Suite.Spec.XMLNAME; + final String ajctestX = suiteX + "/" + AjcTest.Spec.XMLNAME; + final String compileX = ajctestX + "/" + CompilerRun.Spec.XMLNAME; + final String inccompileX = ajctestX + "/" + IncCompilerRun.Spec.XMLNAME; + final String runX = ajctestX + "/" + JavaRun.Spec.XMLNAME; + final String dirchangesX = "*/" + DirChanges.Spec.XMLNAME; + final String messageX = "*/" + SoftMessage.XMLNAME; + final String messageSrcLocX = messageX + "/source-location"; + + // ---- each sub-element needs to be created + // handle messages the same at any level + digester.addObjectCreate(suiteX, AjcTest.Suite.Spec.class.getName()); + digester.addObjectCreate(ajctestX, AjcTest.Spec.class.getName()); + digester.addObjectCreate(compileX, CompilerRun.Spec.class.getName()); + digester.addObjectCreate(compileX + "/file", AbstractRunSpec.WrapFile.class.getName()); + digester.addObjectCreate(inccompileX, IncCompilerRun.Spec.class.getName()); + digester.addObjectCreate(runX, JavaRun.Spec.class.getName()); + digester.addObjectCreate(messageX, SoftMessage.class.getName()); + digester.addObjectCreate(messageSrcLocX, SoftSourceLocation.class.getName()); + digester.addObjectCreate(dirchangesX, DirChanges.Spec.class.getName()); + + // ---- set bean properties for sub-elements created automatically + // -- some remapped - warnings + // - if property exists, map will not be used + digester.addSetProperties(suiteX); // ok to have suite messages and global suite options, etc. + digester.addSetProperties(ajctestX, + new String[] { "title", "dir", "pr"}, + new String[] { "description", "testDirOffset", "bugId"}); + digester.addSetProperties(compileX, + new String[] { "files", "argfiles"}, + new String[] { "paths", "argfiles"}); + digester.addSetProperties(compileX + "/file"); + digester.addSetProperties(inccompileX, "classes", "paths"); + digester.addSetProperties(runX, //"class", "className"); + new String[] { "class", "vm", "skipTester"}, + new String[] { "className", "javaVersion", "skipTester"}); + digester.addSetProperties(dirchangesX); + digester.addSetProperties(messageX); + digester.addSetProperties(messageSrcLocX); + digester.addSetProperties(messageX, "kind", "kindAsString"); + digester.addSetProperties(messageX, "line", "lineAsString"); + // only file subelement of compile uses text as path... XXX vestigial + digester.addCallMethod(compileX + "/file", "setFile", 0); + + // ---- when subelements are created, add to parent + // add ajctest to suite, runs to ajctest, files to compile, messages to any parent... + // the method name (e.g., "addSuite") is in the parent (SuiteHolder) + // the class (e.g., AjcTest.Suite.Spec) refers to the type of the object created + digester.addSetNext(suiteX, "addSuite", AjcTest.Suite.Spec.class.getName()); + digester.addSetNext(ajctestX, "addChild", AjcTest.Spec.class.getName()); + digester.addSetNext(compileX, "addChild", CompilerRun.Spec.class.getName()); + digester.addSetNext(inccompileX, "addChild", IncCompilerRun.Spec.class.getName()); + digester.addSetNext(runX, "addChild", JavaRun.Spec.class.getName()); + digester.addSetNext(compileX + "/file", "addWrapFile", AbstractRunSpec.WrapFile.class.getName()); + digester.addSetNext(messageX, "addMessage", IMessage.class.getName()); + digester.addSetNext(messageSrcLocX, "setSourceLocation", ISourceLocation.class.getName()); + digester.addSetNext(dirchangesX, "addDirChanges", DirChanges.Spec.class.getName()); + + // can set parent, but prefer to have "knows-about" flow down only... + } + + // ------------------------------------------------------------ testing code + /** + * Get expected bean properties for introspection tests. + * This should return an expected property for every attribute in DOCTYPE, + * using any mapped-to names from setupDigester. + */ + static BProps[] expectedProperties() { + return new BProps[] + { + new BProps(AjcTest.Suite.Spec.class, + new String[] { "suiteDir"}), // verbose removed + new BProps(AjcTest.Spec.class, + new String[] { "description", "testDirOffset", "bugId"}), + // mapped from { "title", "dir", "pr"} + new BProps(CompilerRun.Spec.class, + new String[] { "files", "options"}), + new BProps(IncCompilerRun.Spec.class, + new String[] { "tag" }), + new BProps(JavaRun.Spec.class, + new String[] { "className", "skipTester", "options"}), + // mapped from { "class", ...} + new BProps(DirChanges.Spec.class, + new String[] { "added", "removed", "updated", "unchanged", "dirToken", "defaultSuffix"}), + new BProps(AbstractRunSpec.WrapFile.class, + new String[] { "path"}), + new BProps(SoftMessage.class, + new String[] { "kindAsString", "lineAsString", "text", "file"}) + // mapped from { "kind", "line", ...} + }; + } + + /** + * This is only to do compile-time checking for the APIs impliedly + * used in setupDigester(..). + * The property setter checks are redundant with tests based on + * expectedProperties(). + */ + private static void setupDigesterCompileTimeCheck() { + if (true) { throw new Error("never invoked"); } + AjcTest.Suite.Spec suite = new AjcTest.Suite.Spec(); + AjcTest.Spec test = new AjcTest.Spec(); +// AjcTest test = new AjcTest(); +// test.addRunSpec((AbstractRunSpec) null); +//// test.makeIncCompilerRun((IncCompilerRun.Spec) null); +//// test.makeJavaRun((JavaRun.Spec) null); +// test.setDescription((String) null); +// test.setTestBaseDirOffset((String) null); +// test.setBugId((String) null); +// test.setTestSourceLocation((ISourceLocation) null); + + CompilerRun.Spec crunSpec = new CompilerRun.Spec(); + crunSpec.addMessage((IMessage) null); + // XXX crunSpec.addSourceLocation((ISourceLocation) null); + crunSpec.addWrapFile((AbstractRunSpec.WrapFile) null); + crunSpec.setOptions((String) null); + crunSpec.setPaths((String) null); + + IncCompilerRun.Spec icrunSpec = new IncCompilerRun.Spec(); + icrunSpec.addMessage((IMessage) null); + icrunSpec.setTag((String) null); + + JavaRun.Spec jrunspec = new JavaRun.Spec(); + jrunspec.addMessage((IMessage) null); + jrunspec.setClassName((String) null); + jrunspec.addMessage((IMessage) null); + // input s.b. interpretable by Boolean.valueOf(String) + jrunspec.setSkipTester(true); + + DirChanges.Spec dcspec = new DirChanges.Spec(); + dcspec.setAdded((String) null); + dcspec.setRemoved((String) null); + dcspec.setUpdated((String) null); + dcspec.setDefaultSuffix((String) null); + dcspec.setDirToken((String) null); + + SoftMessage m = new SoftMessage(); + m.setSourceLocation((ISourceLocation) null); + m.setText((String) null); + m.setKindAsString((String) null); + + SoftSourceLocation sl = new SoftSourceLocation(); + sl.setFile((String) null); + sl.setLine((String) null); + sl.setColumn((String) null); + sl.setEndLine((String) null); + + // add attribute setters to validate? + } + + /** top element on Digester stack holds the test suite */ + public static class SuiteHolder { + AjcTest.Suite.Spec spec; + public void addSuite(AjcTest.Suite.Spec spec) { + this.spec = spec; + } + } + + /** hold class/properties association for testing */ + static class BProps { + final Class cl; + final String[] props; + BProps(Class cl, String[] props) { + this.cl = cl; + this.props = props; + } + } +} + + diff --git a/testing/src/org/aspectj/testing/xml/IXmlWritable.java b/testing/src/org/aspectj/testing/xml/IXmlWritable.java new file mode 100644 index 000000000..52f43c389 --- /dev/null +++ b/testing/src/org/aspectj/testing/xml/IXmlWritable.java @@ -0,0 +1,25 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.xml; + +/** + * + */ +public interface IXmlWritable { + /** + * Write self out to XML. + * @param out the XMLWriter to write to + */ + void writeXml(XMLWriter out); +} diff --git a/testing/src/org/aspectj/testing/xml/SoftMessage.java b/testing/src/org/aspectj/testing/xml/SoftMessage.java new file mode 100644 index 000000000..e57a4f3de --- /dev/null +++ b/testing/src/org/aspectj/testing/xml/SoftMessage.java @@ -0,0 +1,287 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +package org.aspectj.testing.xml; + +import org.aspectj.bridge.IMessage; +import org.aspectj.bridge.ISourceLocation; +import org.aspectj.bridge.MessageUtil; +import org.aspectj.bridge.SourceLocation; +import org.aspectj.util.LangUtil; + +import java.io.File; +import java.util.Iterator; +import java.util.List; + + +/** + * Implement messages. + * This implementation is immutable if ISourceLocation is immutable. + */ +public class SoftMessage implements IMessage { // XXX mutable dup of Message + public static String XMLNAME = "message"; + public static final File NO_FILE = ISourceLocation.NO_FILE; + private String message; + private IMessage.Kind kind; + private Throwable thrown; + private ISourceLocation sourceLocation; + + + //private ISourceLocation pseudoSourceLocation; // set directly + // collapse enclosed source location for shorter, property-based xml + private String file; + private int line = Integer.MAX_VALUE; + + /** convenience for constructing failure messages */ + public static SoftMessage fail(String message, Throwable thrown) { + return new SoftMessage(message, IMessage.FAIL, thrown, null); + } + + /** + * Print messages. + * @param messages List of IMessage + */ + public static void writeXml(XMLWriter out, List messages) { + if ((null == out) || (null == messages)) { + return; + } + for (Iterator iter = messages.iterator(); iter.hasNext();) { + writeXml(out, (IMessage) iter.next()); + + } + } + + /** + * Print messages. + * @param messages IMessage[] + */ + public static void writeXml(XMLWriter out, IMessage[] messages) { + if ((null == out) || (null == messages)) { + return; + } + for (int i = 0; i < messages.length; i++) { + writeXml(out, messages[i]); + } + } + + /** print message as an element */ + public static void writeXml(XMLWriter out, IMessage message) { // XXX short form only, no files + if ((null == out) || (null == message)) { + return; + } + final String elementName = XMLNAME; + String kindStr = message.getKind().toString(); + String kindAttr = out.makeAttribute("kind",kindStr); + String mStr = message.getMessage(); + if ((null != mStr) && (0 == mStr.length())) { + mStr = null; + } + String mAttr = (null == mStr ? " " : " " + out.makeAttribute("text", mStr)); + int mAttrLen = (null == mStr ? 0 : mAttr.length()); + ISourceLocation sl = message.getISourceLocation(); + String lineStr = (null == sl ? null : "" + sl.getLine()); + String lineAttr = (null == lineStr ? " " : " " + out.makeAttribute("line", lineStr)); + int lineAttrLen = (null == lineStr ? 0 : lineAttr.length()); + int len = (kindAttr.length() + mAttrLen + lineAttrLen); + if (len < 65) { + String s = kindAttr + " " + + ((null == lineStr ? "" : lineAttr) + + (null == mStr ? "" : " " + mAttr)).trim(); + out.printElement(elementName, s); + } else { + out.startElement(elementName, kindAttr + lineAttr, false); + if (0 < mStr.length()) { + out.printAttribute("text", mStr); + } + out.endAttributes(); + out.endElement(elementName); + } + } + + + + public SoftMessage() {} // XXX programmatic only + + /** + * Create a (compiler) error or warning message + * @param message the String used as the underlying message + * @param sourceLocation the ISourceLocation, if any, associated with this message + * @param isError if true, use IMessage.ERROR; else use IMessage.WARNING + */ + public SoftMessage(String message, ISourceLocation location, boolean isError) { + this(message, (isError ? IMessage.ERROR : IMessage.WARNING), null, + location); + } + + /** + * Create a message, handling null values for message and kind + * if thrown is not null. + * @param message the String used as the underlying message + * @param kind the IMessage.Kind of message - not null + * @param thrown the Throwable, if any, associated with this message + * @param sourceLocation the ISourceLocation, if any, associated with this message + * @throws IllegalArgumentException if message is null and + * thrown is null or has a null message, or if kind is null + * and thrown is null. + */ + public SoftMessage(String message, IMessage.Kind kind, Throwable thrown, + ISourceLocation sourceLocation) { + this.message = message; + this.kind = kind; + this.thrown = thrown; + this.sourceLocation = sourceLocation; + if (null == message) { + if (null != thrown) { + message = thrown.getMessage(); + } + if (null == message) { + throw new IllegalArgumentException("null message"); + } + } + if (null == kind) { + throw new IllegalArgumentException("null kind"); + } + } + + /** @return the kind of this message */ + public IMessage.Kind getKind() { + return kind; + } + + /** @return true if kind == IMessage.ERROR */ + public boolean isError() { + return kind == IMessage.ERROR; + } + + /** @return true if kind == IMessage.WARNING */ + public boolean isWarning() { + return kind == IMessage.WARNING; + } + + /** @return true if kind == IMessage.DEBUG */ + public boolean isDebug() { + return kind == IMessage.DEBUG; + } + + /** + * @return true if kind == IMessage.INFO + */ + public boolean isInfo() { + return kind == IMessage.INFO; + } + + /** @return true if kind == IMessage.ABORT */ + public boolean isAbort() { + return kind == IMessage.ABORT; + } + + /** + * @return true if kind == IMessage.FAIL + */ + public boolean isFailed() { + return kind == IMessage.FAIL; + } + + /** @return non-null String with simple message */ + final public String getMessage() { + return message; + } + + /** @return Throwable associated with this message, or null if none */ + final public Throwable getThrown() { + return thrown; + } + + /** + * This returns any ISourceLocation set or a mock-up + * if file and/or line were set. + * @return ISourceLocation associated with this message, + * a mock-up if file or line is available, or null if none + */ + final public ISourceLocation getISourceLocation() { + if ((null == sourceLocation) + && ((null != file) || (line != Integer.MAX_VALUE))) { + File f = (null == file ? NO_FILE : new File(file)); + int line = (this.line == Integer.MAX_VALUE ? 0 : this.line); + sourceLocation = new SourceLocation(f, line); + } + return sourceLocation; + } + + /** set the kind of this message */ + public void setMessageKind(IMessage.Kind kind) { + this.kind = (null == kind ? IMessage.ERROR : kind); + } + + + /** set the file for the underlying source location of this message + * @throws IllegalStateException if source location was set directly + * or indirectly by calling getSourceLocation after setting + * file or line. + */ + public void setFile(String path) { + LangUtil.throwIaxIfFalse(!LangUtil.isEmpty(path), "empty path"); + if (null != sourceLocation) { + throw new IllegalStateException("cannot set line after creating source location"); + } + this.file = path; + } + + /** set the kind of this message */ + public void setKindAsString(String kind) { + setMessageKind(MessageUtil.getKind(kind)); + } + + public void setSourceLocation(ISourceLocation sourceLocation) { + this.sourceLocation = sourceLocation; + } + + /** + * Set the line for the underlying source location. + * @throws IllegalStateException if source location was set directly + * or indirectly by calling getSourceLocation after setting + * file or line. + */ + public void setLineAsString(String line) { + if (null != sourceLocation) { + throw new IllegalStateException("cannot set line after creating source location"); + } + this.line = Integer.valueOf(line).intValue(); + SourceLocation.validLine(this.line); + } + + public void setText(String text) { + this.message = (null == text ? "" : text); + } + + public String toString() { + StringBuffer result = new StringBuffer(); + + result.append(getKind().toString()); + + String messageString = getMessage(); + if (!LangUtil.isEmpty(messageString)) { + result.append(messageString); + } + + ISourceLocation loc = getISourceLocation(); + if ((null != loc) && (loc != ISourceLocation.NO_FILE)) { + result.append(" at " + loc); + } + if (null != thrown) { + result.append(" -- " + LangUtil.renderExceptionShort(thrown)); + } + return result.toString(); + } +} diff --git a/testing/src/org/aspectj/testing/xml/SoftSourceLocation.java b/testing/src/org/aspectj/testing/xml/SoftSourceLocation.java new file mode 100644 index 000000000..2b3dfdad7 --- /dev/null +++ b/testing/src/org/aspectj/testing/xml/SoftSourceLocation.java @@ -0,0 +1,89 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.xml; + + +import org.aspectj.bridge.ISourceLocation; + +import java.io.File; + +/** + * Immutable source location. + * This guarantees that the source file is not null + * and that the numeric values are positive and line <= endLine. + * @see org.aspectj.lang.reflect.SourceLocation + * @see org.aspectj.compiler.base.parser.SourceInfo + * @see org.aspectj.tools.ide.SourceLine + * @see org.aspectj.testing.harness.ErrorLine + */ +public class SoftSourceLocation implements ISourceLocation { // XXX endLine? + public static final File NONE = new File("SoftSourceLocation.NONE"); + private File sourceFile; + private int line; + private int column; + private int endLine; + + public SoftSourceLocation() { + } + + public File getSourceFile() { + return (null != sourceFile ? sourceFile : NONE); + } + public int getLine() { + return line; + } + + public int getColumn() { + return column; + } + + public int getEndLine() { + return line; + } + + public void setFile(String sourceFile) { + this.sourceFile = new File(sourceFile); + } + + public void setLine(String line) { + this.line = convert(line); + if (0 == endLine) { + endLine = this.line; + } + } + + public void setColumn(String column) { + this.column = convert(column); + } + + public void setEndLine(String line) { + this.endLine = convert(line); + } + + + private int convert(String in) { + return Integer.valueOf(in).intValue(); + } + + public String getLocationContext() { + return null; + } + + /** @return String : file:line:column */ + public String toString() { + return getSourceFile().getPath() + ":" + getLine() + ":" + getColumn(); + } + + +} diff --git a/testing/src/org/aspectj/testing/xml/XMLWriter.java b/testing/src/org/aspectj/testing/xml/XMLWriter.java new file mode 100644 index 000000000..fc2eda919 --- /dev/null +++ b/testing/src/org/aspectj/testing/xml/XMLWriter.java @@ -0,0 +1,353 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.xml; + +import org.aspectj.util.LangUtil; + +import java.io.PrintWriter; +import java.util.List; +import java.util.Stack; + +/** + * Manage print stream to an XML document. + * This tracks start/end elements and signals on error, + * and optionally lineates buffer by accumulating + * up to a maximum width (including indent). + * This also has utilities for un/flattening lists and + * rendering buffer. + */ +public class XMLWriter { + static final String SP = " "; + static final String TAB = SP + SP; + + /** maximum value for maxWidth, when flowing buffer */ + public static final int MAX_WIDTH = 8000; + + /** default value for maxWidth, when flowing buffer */ + public static final int DEFAULT_WIDTH = 80; + + /** currently this just strips quotes and ampersands */ + public static String attributeValue(String input) { + input = input.replace('"', '~'); + input = input.replace('&', '='); + return input; + } + + /** @return name="{attributeValue({value})" */ + public static String makeAttribute(String name, String value) { + return (name + "=\"" + attributeValue(value) + "\""); + } + + /** same as flattenList, except also normalize \ -> / */ + public static String flattenFiles(String[] strings) { + return flattenList(strings).replace('\\', '/'); + } + + /** same as flattenList, except also normalize \ -> / */ + public static String flattenFiles(List paths) { + return flattenList(paths).replace('\\', '/'); + } + + /** + * Expand comma-delimited String into list of values, without trimming + * @param list List of items to print - null is silently ignored, + * so for empty items use "" + * @return String[]{} for null input, String[] {input} for input without comma, + * or new String[] {items..} otherwise + * @throws IllegalArgumentException if {any item}.toString() contains a comma + */ + public static String[] unflattenList(String input) { + return (String[]) LangUtil.commaSplit(input).toArray(new String[0]); + } + + /** flatten input and add to list */ + public static void addFlattenedItems(List list, String input) { + LangUtil.throwIaxIfNull(list, "list"); + if (null != input) { + String[] items = XMLWriter.unflattenList(input); + if (!LangUtil.isEmpty(items)) { + for (int i = 0; i < items.length; i++) { + if (!LangUtil.isEmpty(items[i])) { + list.add(items[i]); + } + } + } + } + } + + /** + * Collapse list into a single comma-delimited value (e.g., for list buffer) + * @param list List of items to print - null is silently ignored, + * so for empty items use "" + * @return item{,item}... + * @throws IllegalArgumentException if {any item}.toString() contains a comma + */ + public static String flattenList(List list) { + if ((null == list) || (0 == list.size())) { + return ""; + } + return flattenList(list.toArray()); + } + + + /** + * Collapse list into a single comma-delimited value (e.g., for list buffer) + * @param list the String[] items to print - null is silently ignored, + * so for empty items use "" + * @return item{,item}... + * @throws IllegalArgumentException if list[i].toString() contains a comma + */ + public static String flattenList(Object[] list) { + StringBuffer sb = new StringBuffer(); + if (null != list) { + boolean printed = false; + for (int i = 0; i < list.length; i++) { + Object o = list[i]; + if (null != o) { + if (printed) { + sb.append(","); + } else { + printed = true; + } + String s = o.toString(); + if (-1 != s.indexOf(",")) { + throw new IllegalArgumentException("comma in " + s); + } + sb.append(s); + } + } + } + return sb.toString(); + } + + /** output sink */ + PrintWriter out; + + /** stack of String element names */ + Stack stack = new Stack(); + + /** false if doing attributes */ + boolean attributesDone = true; + + /** current element prefix */ + String indent = ""; + + /** maximum width (in char) of indent and buffer when flowing */ + int maxWidth; + + /** + * Current text being flowed. + * length() is always less than maxWidth. + */ + StringBuffer buffer; + + /** @param out PrintWriter to print to - not null */ + public XMLWriter(PrintWriter out) { + LangUtil.throwIaxIfNull(out, "out"); + this.out = out; + buffer = new StringBuffer(); + maxWidth = DEFAULT_WIDTH; + } + + /** + * Set maximum width (in chars) of buffer to accumulate. + * @param maxWidth int 0..MAX_WIDTH for maximum number of char to accumulate + */ + public void setMaxWidth(int maxWidth) { + if (0 > maxWidth) { + this.maxWidth = 0; + } else if (MAX_WIDTH < maxWidth) { + this.maxWidth = MAX_WIDTH; + } else { + this.maxWidth = maxWidth; + } + } + + /** shortcut for entire element */ + public void printElement(String name, String attributes) { + if (!attributesDone) throw new IllegalStateException("finish attributes"); + if (0 != buffer.length()) { // output on subelement + outPrintln(buffer + ">"); + buffer.setLength(0); + } + String oldIndent = indent; + if (0 < stack.size()) { + indent += TAB; + ((StackElement) stack.peek()).numChildren++; + } + outPrintln(indent + "<" + name + " " + attributes + "/>"); + indent = oldIndent; + } + + /** + * Start element only + * @param name the String label of the element + * @param closeTag if true, delimit the end of the starting tag + */ + public void startElement(String name, boolean closeTag) { + startElement(name, "", closeTag); + } + + /** + * Start element with buffer on the same line. + * This does not flow buffer. + * @param name String tag for the element + * @param attr {name="value"}.. where value + * is a product of attributeValue(String) + */ + public void startElement(String name, String attr, boolean closeTag) { + if (!attributesDone) throw new IllegalStateException("finish attributes"); + LangUtil.throwIaxIfFalse(!LangUtil.isEmpty(name), "empty name"); + + if (0 != buffer.length()) { // output on subelement + outPrintln(buffer + ">"); + buffer.setLength(0); + } + if (0 < stack.size()) { + indent += TAB; + } + StringBuffer sb = new StringBuffer(); + sb.append(indent); + sb.append("<"); + sb.append(name); + + if (!LangUtil.isEmpty(attr)) { + sb.append(" "); + sb.append(attr.trim()); + } + attributesDone = closeTag; + if (closeTag) { + sb.append(">"); + outPrintln(sb.toString()); + } else if (maxWidth <= sb.length()) { + outPrintln(sb.toString()); + } else { + if (0 != this.buffer.length()) { + throw new IllegalStateException("expected empty attributes starting " + name); + } + this.buffer.append(sb.toString()); + } + if (0 < stack.size()) { + ((StackElement) stack.peek()).numChildren++; + } + stack.push(new StackElement(name)); + } + + /** + * @param name should be the same as that given to start the element + * @throws IllegalStateException if start element does not match + */ + public void endElement(String name) { + int level = stack.size(); + String err = null; + StackElement element = null; + if (0 == stack.size()) { + err = "empty stack"; + } else { + element = (StackElement) stack.pop(); + if (!element.name.equals(name)) { + err = "expecting element " + element.name; + } + } + if (null != err) { + err = "endElement(" + name + ") " + stack + ": " + err; + throw new IllegalStateException(err); + } + if (0 < element.numChildren) { + outPrintln(indent + ""); + } else if (0 < buffer.length()) { + outPrintln(buffer + "/>"); + buffer.setLength(0); + } else { + outPrintln(indent + "/>"); + } + if (!attributesDone) { + attributesDone = true; + } + if (0 < stack.size()) { + indent = indent.substring(0, indent.length() - TAB.length()); + } + } + + + /** + * Print name=value if neither is null and name is not empty after trimming, + * accumulating these until they are greater than maxWidth or buffer are + * terminated with endAttributes(..) or endElement(..). + * @param value the String to convert as attribute value - ignored if null + * @param name the String to use as the attribute name + * @throws IllegalArgumentException if name is null or empty after trimming + */ + public void printAttribute(String name, String value) { + if (attributesDone) throw new IllegalStateException("not in attributes"); + if (null == value) { + return; + } + if ((null == name) || (0 == name.trim().length())) { + throw new IllegalArgumentException("no name=" + name + "=" + value); + } + + String newAttr = name + "=\"" + attributeValue(value) + "\""; + int indentLen = indent.length(); + int bufferLen = buffer.length(); + int newAttrLen = (0 == bufferLen ? indentLen : 0) + newAttr.length(); + + if (maxWidth > (bufferLen + newAttrLen)) { + buffer.append(" "); + buffer.append(newAttr); + } else { // at least print old attributes; maybe also new + if (0 < bufferLen) { + outPrintln(buffer.toString()); + buffer.setLength(0); + } + buffer.append(indent + SP + newAttr); + } + } + + public void endAttributes() { + if (attributesDone) throw new IllegalStateException("not in attributes"); + attributesDone = true; + } + + public void printComment(String comment) { + if (!attributesDone) throw new IllegalStateException("in attributes"); + outPrintln(indent + ""); + } + + public void close() { + if (null != out) { + out.close(); + } + + } + + public void println(String string) { + outPrintln(string); + } + + private void outPrintln(String s) { + if (null == out) { + throw new IllegalStateException("used after close"); + } + out.println(s); + } + static class StackElement { + String name; + int numChildren; + public StackElement(String name) { + this.name = name; + } + } + +} diff --git a/testing/testdata/harnessList.txt b/testing/testdata/harnessList.txt new file mode 100644 index 000000000..2b146c125 --- /dev/null +++ b/testing/testdata/harnessList.txt @@ -0,0 +1 @@ +@../../../tests/harnessPasses.txt diff --git a/testing/testdata/suite.dtd b/testing/testdata/suite.dtd new file mode 100644 index 000000000..65025672a --- /dev/null +++ b/testing/testdata/suite.dtd @@ -0,0 +1,6 @@ + + + + + ]> \ No newline at end of file diff --git a/testing/testdata/suite.xml b/testing/testdata/suite.xml new file mode 100644 index 000000000..660af524f --- /dev/null +++ b/testing/testdata/suite.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + ]> + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/testing/testsrc/TestingModuleTests.java b/testing/testsrc/TestingModuleTests.java new file mode 100644 index 000000000..a07fd8617 --- /dev/null +++ b/testing/testsrc/TestingModuleTests.java @@ -0,0 +1,31 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +// default package + +import junit.framework.*; +import junit.framework.Test; + +public class TestingModuleTests extends TestCase { + + public static Test suite() { + TestSuite suite = new TestSuite(TestingModuleTests.class.getName()); + suite.addTest(org.aspectj.testing.util.UtilTests.suite()); + suite.addTest(org.aspectj.testing.xml.TestingXmlTests.suite()); + return suite; + } + + public TestingModuleTests(String name) { super(name); } + +} diff --git a/testing/testsrc/org/aspectj/testing/harness/bridge/AbstractRunSpecTest.java b/testing/testsrc/org/aspectj/testing/harness/bridge/AbstractRunSpecTest.java new file mode 100644 index 000000000..0e5b8651f --- /dev/null +++ b/testing/testsrc/org/aspectj/testing/harness/bridge/AbstractRunSpecTest.java @@ -0,0 +1,65 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.harness.bridge; + +import org.aspectj.testing.run.IRunIterator; +import org.aspectj.testing.xml.XMLWriter; + +import java.io.PrintWriter; +import java.util.List; + +import junit.framework.TestCase; + +/** + * + */ +public class AbstractRunSpecTest extends TestCase { + + public AbstractRunSpecTest(String name) { + super(name); + } + + public void skiptestXmlWrite() { + AbstractRunSpec spec = new TestSpec(); + spec.setOptions("-option1,-option2"); + spec.setKeywords("keyword1, keyword2"); + spec.setPaths("path1.java, path2.java"); + spec.setDescription("some description, with extra"); + XMLWriter out = new XMLWriter(new PrintWriter(System.out)); + spec.writeXml(out); + out.close(); + } + + public void testSetOptions() { + AbstractRunSpec spec = new TestSpec(); + spec.setOptions("1,2"); + List options = spec.getOptionsList(); + String s = "" + options; + assertTrue(s, "[1, 2]".equals(s)); + } + + static class TestSpec extends AbstractRunSpec { + TestSpec() { + super("testspec"); + } + /** + * @see org.aspectj.testing.harness.bridge.AbstractRunSpec#makeRunIterator(Sandbox, Validator) + */ + public IRunIterator makeRunIterator( + Sandbox sandbox, + Validator validator) { + return null; + } + } +} diff --git a/testing/testsrc/org/aspectj/testing/harness/bridge/AjcSpecTest.java b/testing/testsrc/org/aspectj/testing/harness/bridge/AjcSpecTest.java new file mode 100644 index 000000000..635790158 --- /dev/null +++ b/testing/testsrc/org/aspectj/testing/harness/bridge/AjcSpecTest.java @@ -0,0 +1,340 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.harness.bridge; + +import org.aspectj.bridge.IMessage; +import org.aspectj.bridge.IMessageHolder; +import org.aspectj.bridge.ISourceLocation; +import org.aspectj.bridge.MessageHandler; +import org.aspectj.bridge.MessageUtil; +import org.aspectj.testing.harness.bridge.AjcTest.Suite.Spec; +import org.aspectj.testing.xml.AjcSpecXmlReaderTest; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +import junit.framework.Assert; +import junit.framework.AssertionFailedError; +import junit.framework.TestCase; + +/** + * Primarily used by others to test AjcTest + */ +public class AjcSpecTest extends TestCase { + public static final String NOTSAME = " != "; + public static void sameAjcSuiteSpec( + AjcTest.Suite.Spec lhsSpec, + AjcTest.Suite.Spec rhsSpec, + Assert a) { + assertNotNull(lhsSpec); + assertNotNull(rhsSpec); + Iterator lhs = lhsSpec.getChildren().iterator(); + Iterator rhs = rhsSpec.getChildren().iterator(); + while (lhs.hasNext() && rhs.hasNext()) { + AjcTest.Spec lhsTest = (AjcTest.Spec) lhs.next(); + AjcTest.Spec rhsTest = (AjcTest.Spec) rhs.next(); + AjcSpecTest.sameAjcTestSpec(lhsTest, rhsTest, a); + } + a.assertTrue(!lhs.hasNext()); + a.assertTrue(!rhs.hasNext()); + } + + public static void sameAjcTestSpec( + AjcTest.Spec lhsTest, + AjcTest.Spec rhsTest, + Assert a) { + a.assertNotNull(lhsTest); + a.assertNotNull(rhsTest); + a.assertEquals(lhsTest.getBugId(), rhsTest.getBugId()); + a.assertEquals(lhsTest.getTestDirOffset(), rhsTest.getTestDirOffset()); + // XXX suiteDir varies by run.. + sameAbstractRunSpec(lhsTest, rhsTest, a); + } + + public static void sameAbstractRunSpec( + AbstractRunSpec lhs, + AbstractRunSpec rhs, + Assert a) { + a.assertEquals(lhs.description, rhs.description); + // XXX keywords added in .txt reading - + //sameList(lhs.getKeywordsList(), rhs.getKeywordsList(), a); + // XXX sameList(lhs.globalOptions, rhs.globalOptions, a); + sameList(lhs.getOptionsList(), rhs.getOptionsList(), a); + sameList(lhs.getPathsList(), rhs.getPathsList(), a); + // xml adds sourceloc? + //sameSourceLocation(lhs.getSourceLocation(), rhs.getSourceLocation(), a); + // XXX also sourceLocations? + sameMessages(lhs.getMessages(), rhs.getMessages(), a); + } + + /** @return normal form - null is "", "" is "", and others are {fully.qualified.class}.toString().trim() */ + static String normal(Object input) { + if ((null == input) || ("".equals(input))) { + return ""; + } else { + return input.getClass().getName() + "." + input.toString().trim(); + } + } + + /** @return true if these match after normalizing */ + public static void same(Object lhs, Object rhs, Assert a) { + lhs = normal(lhs); + rhs = normal(rhs); + a.assertTrue(lhs + NOTSAME + rhs, lhs.equals(rhs)); + } + + /** @return true if both are empty (null or no entries) or if all match */ + public static void sameRA(String[] lhs, String[] rhs, Assert a) { + if (null == lhs) { + a.assertTrue((null == rhs) || (0 == rhs.length)); + } else if (null == rhs) { + a.assertTrue(0 == lhs.length); + } else { + String l = normal(lhs); + String r = normal(rhs); + a.assertTrue(l + NOTSAME + r, l.equals(r)); + } + } + + /** @return normal form for String[] items*/ + static String normal(String[] items) { + return (null == items ? "[]" : normal(Arrays.asList(items))); + } + + /** @return normal form for list items */ + static String normal(List list) { + StringBuffer sb = new StringBuffer(); + sb.append("["); + boolean first = true; + for (Iterator iter = list.iterator(); iter.hasNext();) { + Object o = iter.next(); + if (!first) { + sb.append(", "); + } else { + first = false; + } + sb.append(normal(o)); + } + sb.append("]"); + return sb.toString(); + } + + /** @return true if both are empty (null or no entries) or if all match after trimming */ + public static void sameListSize(List lhs, List rhs, Assert a) { + if (null == lhs) { + a.assertTrue((null == rhs) || (0 == rhs.size())); + } else if (null == rhs) { + a.assertTrue(0 == lhs.size()); + } else { + a.assertTrue(rhs.size() == lhs.size()); + } + } + + /** @return true if both are empty (null or no entries) or if all match after trimming */ + public static void sameList(List lhs, List rhs, Assert a) { + sameListSize(lhs, rhs, a); + String l = normal(lhs); + String r = normal(rhs); + String label = l + NOTSAME + r; + a.assertTrue(label, l.equals(r)); + } + +// /** +// * Normalize and compare: +// *
  • bug id's are not compared since extracted during xml writing
  • +// *
  • keyword compare is disabled since keywords are generated during xml reading.
  • +// *
  • description compare is normalized by stripping bug ids
  • +// *
  • String and arrays are equal when empty (null or 0-length)
  • +// * @see Ajctest#stripBugId(String) +// */ +// public static void sameAjcTest(AjcTest lhs, AjcTest rhs, Assert reporter) { +// Assert a = reporter; +// String label = lhs + NOTSAME + rhs; +// a.assertTrue(label, null != lhs); +// a.assertTrue(label, null != rhs); +// //a.assertTrue(label, lhs.ignoreWarnings == rhs.ignoreWarnings); +// // XXX disabled - not in .txt +// // sameStringList(lhs.keywords, rhs.keywords, a); +// // sameString(lhs.bugId, rhs.bugId, a); +// // argh - bugid stripped from description +// //same(AjcTest.stripBugId(lhs.description), AjcTest.stripBugId(lhs.description), a); +// //sameRA(lhs.globals, rhs.globals, a); +// //lhs.reset(); +// //rhs.reset(); +// boolean gotOne = false; +// IMessageHolder holder = new MessageHandler(); +// a.assertTrue(label, !holder.hasAnyMessage(IMessage.FAIL, IMessageHolder.ORGREATER)); +// while (lhs.hasNextRun() && rhs.hasNextRun()) { +// sameIAjcRun((IAjcRun) lhs.nextRun(holder), (IAjcRun) rhs.nextRun(holder), reporter); +// a.assertTrue(label, !holder.hasAnyMessage(IMessage.FAIL, IMessageHolder.ORGREATER)); +// if (!gotOne) { +// gotOne = true; +// } +// } +// a.assertTrue(label, gotOne); +// a.assertTrue(label, !lhs.hasNextRun()); +// a.assertTrue(label, !rhs.hasNextRun()); +// } + + public static void sameIAjcRun(IAjcRun lhs, IAjcRun rhs, Assert reporter) { + Assert a = reporter; + a.assertTrue(lhs != null); + a.assertTrue(rhs != null); + Class c = lhs.getClass(); + a.assertTrue(c == rhs.getClass()); + AbstractRunSpec lhsSpec; + AbstractRunSpec rhsSpec; + + if (c == CompilerRun.class) { + CompilerRun.Spec l = ((CompilerRun) lhs).spec; + CompilerRun.Spec r = ((CompilerRun) rhs).spec; + lhsSpec = l; + rhsSpec = r; + a.assertEquals(l.testSrcDirOffset, r.testSrcDirOffset); + a.assertEquals(l.compiler, r.compiler); + } else if (c == JavaRun.class) { + JavaRun.Spec l = ((JavaRun) lhs).spec; + JavaRun.Spec r = ((JavaRun) rhs).spec; + lhsSpec = l; + rhsSpec = r; + a.assertTrue(l.skipTester == r.skipTester); + a.assertEquals(l.className, r.className); + } else if (c == IncCompilerRun.class) { + IncCompilerRun.Spec l = ((IncCompilerRun) lhs).spec; + IncCompilerRun.Spec r = ((IncCompilerRun) rhs).spec; + lhsSpec = l; + rhsSpec = r; + } else { + assertTrue(lhs.equals(rhs)); + return; + } + sameSpec(lhsSpec, rhsSpec, reporter); + } + + public static void sameSpec(AbstractRunSpec lhs, AbstractRunSpec rhs, Assert a) { + if ((null == lhs) && (null == rhs)) { + return; + } + a.assertTrue(lhs != null); + a.assertTrue(rhs != null); + a.assertEquals(""+lhs.getOptionsList(), ""+rhs.getOptionsList()); + sameList(lhs.getPathsList(), rhs.getPathsList(), a); + sameMessages(lhs.getMessages(), rhs.getMessages(), a); + sameDirChangesList(lhs.dirChanges, rhs.dirChanges, a); + } + + public static void sameDirChangesList(ArrayList lhs, ArrayList rhs, Assert a) { + if ((null == lhs) && (null == rhs)) { + return; + } + a.assertTrue(rhs != null); + a.assertTrue(lhs != null); + sameListSize(lhs, rhs, a); + Iterator lhsIter = lhs.iterator(); + Iterator rhsIter = rhs.iterator(); + while (lhsIter.hasNext() && rhsIter.hasNext()) { + sameDirChangesSpec((DirChanges.Spec) lhsIter.next(), (DirChanges.Spec) rhsIter.next(), a); + } + } + + public static void sameDirChangesSpec(DirChanges.Spec lhs, DirChanges.Spec rhs, Assert a) { + if ((null == lhs) && (null == rhs)) { + return; + } + a.assertTrue(rhs != null); + a.assertTrue(lhs != null); + a.assertEquals(lhs.defaultSuffix, rhs.defaultSuffix); + a.assertEquals(lhs.dirToken, rhs.dirToken); + sameList(lhs.updated, rhs.updated, a); + sameList(lhs.removed, rhs.removed, a); + sameList(lhs.added, rhs.added, a); + } + + public static void sameMessages(List one, List two, Assert a) { + if ((null == one) && (null == two)) { + return; + } + Iterator lhs = one.iterator(); + Iterator rhs = two.iterator(); + while (lhs.hasNext() && rhs.hasNext()) { + sameMessage((IMessage) lhs.next(), (IMessage) rhs.next(), a); + } + assertTrue(!lhs.hasNext()); + assertTrue(!rhs.hasNext()); + } + + public static void sameMessage(IMessage lhs, IMessage rhs, Assert a) { + if ((null == lhs) && (null == rhs)) { + return; + } + a.assertTrue(lhs != null); + a.assertTrue(rhs != null); + a.assertTrue(lhs.getKind() == rhs.getKind()); + same(lhs.getMessage(), rhs.getMessage(), a); + a.assertEquals(lhs.getThrown(), rhs.getThrown()); + sameSourceLocation(lhs.getISourceLocation(), rhs.getISourceLocation(), a); + } + + public static void sameSourceLocation(ISourceLocation lhs, ISourceLocation rhs, Assert a) { + if ((null == lhs) && (null == rhs)) { + return; + } + a.assertTrue(lhs != null); + a.assertTrue(rhs != null); + a.assertTrue(lhs.getLine() == rhs.getLine()); + a.assertTrue(lhs.getColumn() == rhs.getColumn()); + a.assertTrue(lhs.getEndLine() == rhs.getEndLine()); + // XXX need to compare files, permitting null == NONE + } + + /** + * Constructor for AjcSpecTest. + * @param name + */ + public AjcSpecTest(String name) { + super(name); + } + + public void testMinimal() { + AjcTest.Spec one = new AjcTest.Spec(); + AjcTest.Spec two = new AjcTest.Spec(); + // empty/identity tests + sameAjcTestSpec(one, two, this); + + one.addOption("-one"); + one.addKeyword("keyword"); + one.addPath("path"); + IMessage m = MessageUtil.info("info message"); + one.addMessage(m); + DirChanges.Spec dcspec = new DirChanges.Spec(); + dcspec.setDirToken("dirToken"); + dcspec.setDefaultSuffix(".suffix"); + one.addDirChanges(dcspec); + + // full/identity tests + sameAjcTestSpec(one, one, this); + // XXX need to clone... + + // XXX need to test that more differences are detected + boolean passed = false; + try { + sameAjcTestSpec(one, two, this); + } catch (AssertionFailedError e) { + passed = true; + } + assertTrue("did not get expected exception", passed); + } +} diff --git a/testing/testsrc/org/aspectj/testing/harness/bridge/CompilerRunSpecTest.java b/testing/testsrc/org/aspectj/testing/harness/bridge/CompilerRunSpecTest.java new file mode 100644 index 000000000..ddd908b33 --- /dev/null +++ b/testing/testsrc/org/aspectj/testing/harness/bridge/CompilerRunSpecTest.java @@ -0,0 +1,163 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.harness.bridge; + +import org.aspectj.bridge.MessageHandler; + +import junit.framework.TestCase; + +/** + * + */ +public class CompilerRunSpecTest extends TestCase { + + /** + * Constructor for CompilerRunSpecTest. + * @param name + */ + public CompilerRunSpecTest(String name) { + super(name); + } + + public void testSetupArgs() { + checkSetupArgs("verbose", false); + checkSetupArgs("lenient", false); + checkSetupArgs("strict", false); + checkSetupArgs("ajc", true); // XXX need to predict/test compiler selection + checkSetupArgs("eclipse", true); + } + + void checkSetupArgs(String arg, boolean isTestArg) { + MessageHandler handler = new MessageHandler(); + CompilerRun.Spec spec = new CompilerRun.Spec(); + AbstractRunSpec.RT parentRuntime = new AbstractRunSpec.RT(); + String result; + String expResult; + + // -------- local set + // global ^ (force-off) to disable + spec.setOptions("-" + arg); + parentRuntime.setOptions(new String[] {"^" + arg}); + assertTrue(spec.adoptParentValues(parentRuntime, handler)); + if (0 != handler.numMessages(null, true)) { + assertTrue(handler.toString(), false); + } + result = ""+spec.testSetup.commandOptions; + assertTrue(result, "[]".equals(result)); + + // global ! (force-on) does not change local-set + parentRuntime.setOptions(new String[] {"!" + arg}); + assertTrue(spec.adoptParentValues(parentRuntime, handler)); + if (0 != handler.numMessages(null, true)) { + assertTrue(handler.toString(), false); + } + result = ""+spec.testSetup.commandOptions; + expResult = (isTestArg ? "[]" : "[-" + arg + "]"); + assertTrue(result, expResult.equals(result)); + + // global - (set) does not change local-set + parentRuntime.setOptions(new String[] {"-" + arg}); + assertTrue(spec.adoptParentValues(parentRuntime, handler)); + if (0 != handler.numMessages(null, true)) { + assertTrue(handler.toString(), false); + } + result = ""+spec.testSetup.commandOptions; + expResult = (isTestArg ? "[]" : "[-" + arg + "]"); + assertTrue(result, expResult.equals(result)); + + // global (unset) does not change local-set + parentRuntime.setOptions(new String[] {""}); + assertTrue(spec.adoptParentValues(parentRuntime, handler)); + if (0 != handler.numMessages(null, true)) { + assertTrue(handler.toString(), false); + } + result = ""+spec.testSetup.commandOptions; + expResult = (isTestArg ? "[]" : "[-" + arg + "]"); + assertTrue(result, expResult.equals(result)); + + // -------- local force-on + // global ^ (force-off) conflicts with local force-on + spec.setOptions("!" + arg); + parentRuntime.setOptions(new String[] {"^" + arg}); + assertTrue(!spec.adoptParentValues(parentRuntime, handler)); + assertTrue(0 != handler.numMessages(null, true)); + handler.init(); + + // global ! (force-on) does not change local force-on + parentRuntime.setOptions(new String[] {"!" + arg}); + assertTrue(spec.adoptParentValues(parentRuntime, handler)); + if (0 != handler.numMessages(null, true)) { + assertTrue(handler.toString(), false); + } + result = ""+spec.testSetup.commandOptions; + expResult = (isTestArg ? "[]" : "[-" + arg + "]"); + assertTrue(result, expResult.equals(result)); + + // global - (set) does not change local force-on + parentRuntime.setOptions(new String[] {"-" + arg}); + assertTrue(spec.adoptParentValues(parentRuntime, handler)); + if (0 != handler.numMessages(null, true)) { + assertTrue(handler.toString(), false); + } + result = ""+spec.testSetup.commandOptions; + expResult = (isTestArg ? "[]" : "[-" + arg + "]"); + assertTrue(result, expResult.equals(result)); + + // global (unset) does not change local force-on + parentRuntime.setOptions(new String[] {""}); + assertTrue(spec.adoptParentValues(parentRuntime, handler)); + if (0 != handler.numMessages(null, true)) { + assertTrue(handler.toString(), false); + } + result = ""+spec.testSetup.commandOptions; + expResult = (isTestArg ? "[]" : "[-" + arg + "]"); + assertTrue(result, expResult.equals(result)); + + + // -------- local force-off + // global ^ (force-off) does not change local force-off + spec.setOptions("^" + arg); + parentRuntime.setOptions(new String[] {"^" + arg}); + assertTrue(spec.adoptParentValues(parentRuntime, handler)); + if (0 != handler.numMessages(null, true)) { + assertTrue(handler.toString(), false); + } + result = ""+spec.testSetup.commandOptions; + assertTrue(result, ("[]").equals(result)); + + // global ! (force-on) conflicts with local force-off + parentRuntime.setOptions(new String[] {"!" + arg}); + assertTrue(!spec.adoptParentValues(parentRuntime, handler)); + assertTrue(0 != handler.numMessages(null, true)); + handler.init(); + + // global - (set) overridden by local force-off // XXX?? + parentRuntime.setOptions(new String[] {"-" + arg}); + assertTrue(spec.adoptParentValues(parentRuntime, handler)); + if (0 != handler.numMessages(null, true)) { + assertTrue(handler.toString(), false); + } + result = ""+spec.testSetup.commandOptions; + assertTrue(result, ("[]").equals(result)); + + // global (unset) does not change local force-off + parentRuntime.setOptions(new String[] {""}); + assertTrue(spec.adoptParentValues(parentRuntime, handler)); + if (0 != handler.numMessages(null, true)) { + assertTrue(handler.toString(), false); + } + result = ""+spec.testSetup.commandOptions; + assertTrue(result, ("[]").equals(result)); + } +} diff --git a/testing/testsrc/org/aspectj/testing/harness/bridge/ParseTestCase.java b/testing/testsrc/org/aspectj/testing/harness/bridge/ParseTestCase.java new file mode 100644 index 000000000..c8a708e13 --- /dev/null +++ b/testing/testsrc/org/aspectj/testing/harness/bridge/ParseTestCase.java @@ -0,0 +1,228 @@ +/* ******************************************************************* + * Copyright (c) 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.harness.bridge; + +import org.aspectj.bridge.IMessage; +import org.aspectj.bridge.IMessageHandler; +import org.aspectj.bridge.IMessageHolder; +import org.aspectj.bridge.ISourceLocation; +import org.aspectj.bridge.Message; +import org.aspectj.bridge.MessageHandler; +import org.aspectj.bridge.MessageUtil; +import org.aspectj.bridge.SourceLocation; +import org.aspectj.testing.run.IRun; +import org.aspectj.testing.run.IRunIterator; +import org.aspectj.testing.run.IRunListener; +import org.aspectj.testing.run.IRunStatus; +import org.aspectj.testing.run.IRunValidator; +import org.aspectj.testing.run.RunStatus; +import org.aspectj.testing.run.RunValidator; +import org.aspectj.testing.run.Runner; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import junit.framework.TestCase; + +public class ParseTestCase extends TestCase { + + public ParseTestCase(String name) { + super(name); + } + + + public void testParse() throws Exception { // XXX failing b/c of iteration + Runner runner = new Runner(); + IMessageHolder handler = new MessageHandler(); + RunStatus status; + Validator validator = new Validator(handler); + final File suiteFile = new File("../testing/testdata/suite.xml"); + List tests = parseSuite(suiteFile); + Sandbox sandbox = new Sandbox(new File("testdata"), validator); + IRunListener listenerNULL = null; + ISourceLocation sl = new SourceLocation(suiteFile, 0, 0,0); + for (Iterator iter = tests.iterator(); iter.hasNext();) { + status = new RunStatus(handler, runner); + AjcTest.Spec test = (AjcTest.Spec) iter.next(); + test.setSourceLocation(sl); + IRunIterator child = test.makeRunIterator(sandbox, validator); + //test.setup(new String[0], validator); // XXX + //IRun child = runner.wrap(test, null); + // huh? runIterator not generating child status? + //RunStatus childStatus = runner.makeChildStatus(); + runner.runIterator(child, status, listenerNULL); + MessageUtil.print(System.err, status); + } + } + + private List parseSuite(File file) throws ParserConfigurationException, IOException, SAXException{ + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setValidating(true); + factory.setIgnoringElementContentWhitespace(true); + factory.setIgnoringComments(true); + + + DocumentBuilder builder = factory.newDocumentBuilder(); + System.out.println(file.getAbsoluteFile()); + Document doc = builder.parse(file); + + dump(doc.getDocumentElement(), 0); + + List ret = new ArrayList(); + Node suiteNode = doc.getDocumentElement(); + + NodeList children = suiteNode.getChildNodes(); + for (int i=0; i < children.getLength(); i++) { + ret.add(parseTest(children.item(i))); + } + + return ret; + } + + private AjcTest.Spec parseTest(Node node) { + String title = getAttributeString(node, "title"); + String pr = getAttributeString(node, "pr"); + String dir = getAttributeString(node, "dir"); + + ISourceLocation sourceLocation = + new SourceLocation(new File("Missing"), 0, 0, 0); + AjcTest.Spec test = new AjcTest.Spec(); + test.setDescription(title); + test.setTestDirOffset(dir); + test.setBugId(Integer.valueOf(pr).intValue()); + test.setSourceLocation(sourceLocation); + //AjcTest test = new AjcTest(title, dir, pr, sourceLocation); + + System.out.println(test); + + List ret = new ArrayList(); + + NodeList children = node.getChildNodes(); + for (int i=0; i < children.getLength(); i++) { + test.addChild(parseIRun(test, children.item(i), dir)); +// test.addRunSpec(parseIRun(test, children.item(i), dir)); + } + + return test; + } + + private IRunSpec parseIRun(AjcTest.Spec test, Node node, String baseDir) { + String kind = node.getNodeName(); + if (kind.equals("compile")) { + List args = parseChildrenStrings(node, "arg"); + List files = parseChildrenStrings(node, "file"); + List expectedMessages = parseChildrenMessages(node); + CompilerRun.Spec spec = new CompilerRun.Spec(); + spec.addOptions((String[]) args.toArray(new String[0])); + spec.addPaths((String[]) args.toArray(new String[0])); + spec.addMessages(expectedMessages); + spec.testSrcDirOffset = null; // baseDir; + return spec; + } else if (kind.equals("run")) { + JavaRun.Spec spec = new JavaRun.Spec(); + spec.className = getAttributeString(node, "class"); + spec.addOptions(new String[0]); //??? could add support here + JavaRun run = new JavaRun(spec); + return spec; + } + + return null; + } + + private List parseChildrenMessages(Node node) { + List ret = new ArrayList(); + + NodeList children = node.getChildNodes(); + for (int i=0; i < children.getLength(); i++) { + Node child = children.item(i); + if (child.getNodeName().equals("message")) { + ret.add(parseMessage(child)); + } + } + return ret; + } + + private IMessage parseMessage(Node child) { + IMessage.Kind kind; + String sKind = getAttributeString(child, "kind"); + if (sKind.equals("error")) { kind = IMessage.ERROR; } + else if (sKind.equals("warning")) { kind = IMessage.WARNING; } + else { + throw new RuntimeException("unknown kind: " + sKind); + } + String filename = getAttributeString(child, "file"); + File file; + if (filename != null) { + file = new File(filename); + } else { + file = new File("XXX"); //XXX + } + + int line = Integer.valueOf(getAttributeString(child, "line")).intValue(); + + ISourceLocation sourceLocation = new SourceLocation(file, line, line, 0); + + return new Message("", kind, null, sourceLocation); + } + + + + private List parseChildrenStrings(Node node, String kind) { + List ret = new ArrayList(); + + NodeList children = node.getChildNodes(); + for (int i=0; i < children.getLength(); i++) { + Node child = children.item(i); + if (child.getNodeName().equals(kind)) { + Node first = child.getFirstChild(); + if (null != first) { + ret.add(first.getNodeValue());// XXX + } + } + } + return ret; + } + + + + private String getAttributeString(Node node, String name) { + Node attrNode = node.getAttributes().getNamedItem(name); + if (attrNode == null) return null; + return attrNode.getNodeValue(); + } + + + + + private void dump(Node node, int indent) { + for (int i=0; i < indent; i++) System.out.print(" "); + System.out.println(node); + NodeList children = node.getChildNodes(); + for (int i=0; i < children.getLength(); i++) { + dump(children.item(i), indent+1); + } + } + + +} diff --git a/testing/testsrc/org/aspectj/testing/harness/bridge/TestingBridgeTests.java b/testing/testsrc/org/aspectj/testing/harness/bridge/TestingBridgeTests.java new file mode 100644 index 000000000..05d64b01d --- /dev/null +++ b/testing/testsrc/org/aspectj/testing/harness/bridge/TestingBridgeTests.java @@ -0,0 +1,32 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +package org.aspectj.testing.harness.bridge; + +import junit.framework.*; + +public class TestingBridgeTests extends TestCase { + + public static Test suite() { + TestSuite suite = new TestSuite(TestingBridgeTests.class.getName()); + //$JUnit-BEGIN$ + suite.addTestSuite(AjcSpecTest.class); + suite.addTestSuite(ParseTestCase.class); + //$JUnit-END$ + return suite; + } + + public TestingBridgeTests(String name) { super(name); } + +} diff --git a/testing/testsrc/org/aspectj/testing/util/BridgeUtilTest.java b/testing/testsrc/org/aspectj/testing/util/BridgeUtilTest.java new file mode 100644 index 000000000..ae0a7cdac --- /dev/null +++ b/testing/testsrc/org/aspectj/testing/util/BridgeUtilTest.java @@ -0,0 +1,100 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.util; + +import org.aspectj.bridge.MessageHandler; +import org.aspectj.bridge.MessageUtil; +import org.aspectj.testing.run.IRun; +import org.aspectj.testing.run.IRunStatus; +import org.aspectj.testing.run.RunStatus; +import org.aspectj.testing.run.Runner; + +import junit.framework.TestCase; + +/** + * + */ +public class BridgeUtilTest extends TestCase { + + public BridgeUtilTest(String name) { + super(name); + } + + public void testChildString() { + String expect; + String id; + id = "run status identifier"; + expect = "PASS " + id + " 0 tests"; + checkChildString(id, 0, 0, 0, 0, expect); + expect = "PASS " + id + " 2 tests (2 skipped)"; + checkChildString(id, 2, 0, 0, 0, expect); + expect = "PASS " + id + " 3 tests (1 skipped, 2 passed)"; + checkChildString(id, 1, 0, 0, 2, expect); + expect = "FAIL " + id + " 3 tests (1 skipped, 2 failed)"; + checkChildString(id, 1, 0, 2, 0, expect); + expect = "FAIL " + id + " 6 tests (1 skipped, 2 failed, 3 passed)"; + checkChildString(id, 1, 0, 2, 3, expect); + expect = "FAIL " + id + " 1 tests (1 failed)"; + checkChildString(id, 0, 0, 1, 0, expect); + expect = "FAIL " + id + " 4 tests (1 failed, 3 passed)"; + checkChildString(id, 0, 0, 1, 3, expect); + expect = "PASS " + id + " 1 tests (1 passed)"; + checkChildString(id, 0, 0, 0, 1, expect); + + // "incomplete" variants + expect = "PASS " + id + " 5 tests (5 incomplete)"; + checkChildString(id, 0, 5, 0, 0, expect); + expect = "PASS " + id + " 7 tests (2 skipped, 5 incomplete)"; + checkChildString(id, 2, 5, 0, 0, expect); + expect = "PASS " + id + " 8 tests (1 skipped, 5 incomplete, 2 passed)"; + checkChildString(id, 1, 5, 0, 2, expect); + expect = "FAIL " + id + " 8 tests (1 skipped, 5 incomplete, 2 failed)"; + checkChildString(id, 1, 5, 2, 0, expect); + expect = "FAIL " + id + " 11 tests (1 skipped, 5 incomplete, 2 failed, 3 passed)"; + checkChildString(id, 1, 5, 2, 3, expect); + expect = "FAIL " + id + " 6 tests (5 incomplete, 1 failed)"; + checkChildString(id, 0, 5, 1, 0, expect); + expect = "FAIL " + id + " 9 tests (5 incomplete, 1 failed, 3 passed)"; + checkChildString(id, 0, 5, 1, 3, expect); + expect = "PASS " + id + " 6 tests (5 incomplete, 1 passed)"; + checkChildString(id, 0, 5, 0, 1, expect); + } + + void checkChildString(String id, int numSkips, int numIncomplete, int numFails, int numPasses, + String expected) { + Runner runner = new Runner(); + MessageHandler holder = new MessageHandler(); + RunStatus status = new RunStatus(holder, runner); + status.setIdentifier(id); + status.start(); + + final IRun failer = new IRun() { + public boolean run(IRunStatus status) { return false; } + }; + final IRun passer = new IRun() { + public boolean run(IRunStatus status) { return true; } + }; + final Object result = (numFails > 0 ? IRunStatus.FAIL : IRunStatus.PASS); + while (numFails-- > 0) { + runner.runChild(failer,status, null, null); + } + while (numPasses-- > 0) { + runner.runChild(passer,status, null, null); + } + status.finish(result); + String actual = BridgeUtil.childString(status, numSkips, numIncomplete); + String label = " expected \"" + expected + "\" got \"" + actual + "\""; + assertTrue(label, expected.equals(actual)); + } +} diff --git a/testing/testsrc/org/aspectj/testing/util/FileUtilTest.java b/testing/testsrc/org/aspectj/testing/util/FileUtilTest.java new file mode 100644 index 000000000..1ff4d6cf6 --- /dev/null +++ b/testing/testsrc/org/aspectj/testing/util/FileUtilTest.java @@ -0,0 +1,46 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.util; + +import java.io.File; + +import junit.framework.TestCase; + +/** + * + */ +public class FileUtilTest extends TestCase { + + /** + * Constructor for FileUtilTest. + * @param arg0 + */ + public FileUtilTest(String arg0) { + super(arg0); + } + + public void testFileEquals() { + // File.equals(..) is based on lexical compare of filenames +// File rf = new File("testsrc/org/aspectj/testing/util/FileUtilTest.java"); +// File rb = new File("testsrc\\org\\aspectj\\testing\\util\\FileUtilTest.java"); +// String a = rf.getAbsolutePath().replace('\\', '/'); +// File af = new File(a); +// File ab = new File(a.replace('/', '\\')); +// list.add(af); +// list.add(ab); +// list.add(rb); +// assertTrue(list.contains(duplicateTwo)); +// assertTrue(list.contains(anotherOne)); + } +} diff --git a/testing/testsrc/org/aspectj/testing/util/IteratorWrapperTest.java b/testing/testsrc/org/aspectj/testing/util/IteratorWrapperTest.java new file mode 100644 index 000000000..fdcd695ac --- /dev/null +++ b/testing/testsrc/org/aspectj/testing/util/IteratorWrapperTest.java @@ -0,0 +1,164 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.util; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import junit.framework.TestCase; + +/** + * + */ +public class IteratorWrapperTest extends TestCase { + + /** + * Constructor for IteratorWrapperTest. + * @param name + */ + public IteratorWrapperTest(String name) { + super(name); + } + + public void testIteratorWrapper() { + Object[][] exp = new Object[][] {}; + List[] in = new List[] {}; + checkIteratorWrapper(in, exp); + + in = new List[] {Collections.EMPTY_LIST}; + checkIteratorWrapper(in, exp); + + in = new List[] {Collections.EMPTY_LIST, Collections.EMPTY_LIST}; + checkIteratorWrapper(in, exp); + + Object[] ra1 = new Object[] { "1" }; + List one = Collections.unmodifiableList(Arrays.asList(ra1)); + in = new List[] {one}; + exp = new Object[][] { ra1 }; + checkIteratorWrapper(in, exp); + + in = new List[] {one, one}; + exp = new Object[][] { new Object[] { "1", "1"} }; + checkIteratorWrapper(in, exp); + + Object[] RA_ab = new String[] { "a", "b" }; + List List_ab = Collections.unmodifiableList(Arrays.asList(RA_ab)); + in = new List[] {List_ab}; + exp = new Object[][] { + new Object[] { "a" }, + new Object[] { "b" } + }; + checkIteratorWrapper(in, exp); + + in = new List[] {one, List_ab}; + exp = new Object[][] { + new Object[] { "1", "a" }, + new Object[] { "1", "b" }, + }; + checkIteratorWrapper(in, exp); + + Object[] RA_cd = new String[] { "c", "d" }; + List List_cd = Collections.unmodifiableList(Arrays.asList(RA_cd)); + + in = new List[] {List_ab, List_cd}; + exp = new Object[][] { + new Object[] { "a", "c" }, + new Object[] { "b", "c" }, + new Object[] { "a", "d" }, + new Object[] { "b", "d" } + }; + checkIteratorWrapper(in, exp); + + in = new List[] {one, one, one}; + exp = new Object[][] { + new Object[] { "1", "1", "1" } + }; + checkIteratorWrapper(in, exp); + + in = new List[] {List_ab, List_ab, List_ab}; + exp = new Object[][] { + new Object[] { "a", "a", "a" }, + new Object[] { "b", "a", "a" }, + new Object[] { "a", "b", "a" }, + new Object[] { "b", "b", "a" }, + new Object[] { "a", "a", "b" }, + new Object[] { "b", "a", "b" }, + new Object[] { "a", "b", "b" }, + new Object[] { "b", "b", "b" } + }; + checkIteratorWrapper(in, exp); + + in = new List[] {one, List_ab, List_ab}; + exp = new Object[][] { + new Object[] { "1", "a", "a" }, + new Object[] { "1", "b", "a" }, + new Object[] { "1", "a", "b" }, + new Object[] { "1", "b", "b" }, + }; + checkIteratorWrapper(in, exp); + + in = new List[] {one, List_ab, one}; + exp = new Object[][] { + new Object[] { "1", "a", "1" }, + new Object[] { "1", "b", "1" } + }; + checkIteratorWrapper(in, exp); + + in = new List[] {List_ab, one, List_ab}; + exp = new Object[][] { + new Object[] { "a", "1", "a" }, + new Object[] { "b", "1", "a" }, + new Object[] { "a", "1", "b" }, + new Object[] { "b", "1", "b" } + }; + checkIteratorWrapper(in, exp); + + in = new List[] {List_ab, one, List_ab, List_ab, Collections.EMPTY_LIST}; + exp = new Object[][] {}; + checkIteratorWrapper(in, exp); + + } + + void checkIteratorWrapper(List[] lists, Object[][] exp) { + IteratorWrapper it = new IteratorWrapper(lists); + for (int i = 0; i < exp.length; i++) { + Object[] e = exp[i]; + if (!it.hasNext()) { + String s = "exp[" + i + "]: " + Arrays.asList(e) + " it=" + it; + assertTrue(s, false); + } + Object[] actual = (Object[]) it.next(); + checkEquals(e, actual, i); + } + if (it.hasNext()) { + String s = "> " + exp.length + " it=" + it; + assertTrue(s, false); + } + } + + void checkEquals(Object[] exp, Object[] actual, int index) { + if (null == exp) { + assertTrue(null == actual); + } else { + assertTrue(null != actual); + } + String s = "] exp=" + Arrays.asList(exp) + " act=" + Arrays.asList(actual); + assertTrue(s, exp.length == actual.length); + for (int i = 0; i < actual.length; i++) { + assertTrue(null != exp[i]); + assertTrue("[" + index + ", " + i + s, exp[i].equals(actual[i])); + } + } +} diff --git a/testing/testsrc/org/aspectj/testing/util/LangUtilTest.java b/testing/testsrc/org/aspectj/testing/util/LangUtilTest.java new file mode 100644 index 000000000..ebdb21cc5 --- /dev/null +++ b/testing/testsrc/org/aspectj/testing/util/LangUtilTest.java @@ -0,0 +1,342 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.util; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.StringTokenizer; + +import junit.framework.TestCase; +import junit.textui.TestRunner; + +/** + * + * @author isberg + */ +public class LangUtilTest extends TestCase { + + private static final String ME + = "org.aspectj.testing.util.LangUtilTest"; + + /** @param args ignored */ + public static void main(String[] args) { + TestRunner.main(new String[] {ME}); + } + + /** + * Constructor for LangUtilTest. + * @param name + */ + public LangUtilTest(String name) { + super(name); + } + + void check(String l, StringTokenizer st, int max, String delim) { + for (int i = 0; i < max; i++) { + if ((i > 0) && (null != delim)) { + assertEquals(l, delim, st.nextToken()); + } + assertEquals(l, ""+i, st.nextToken()); + } + assertTrue(l, !st.hasMoreTokens()); + } + + + void checkUnflatten(FTest test) { + String[] exp = test.unflattened; + ArrayList result = LangUtil.unflatten(test.toUnflatten, test.spec); + String label = test + " -> " + result; + assertNotNull(label, result); + + assertEquals(label, exp.length, result.size()); + for (int i = 0; i < exp.length; i++) { + assertEquals(label, exp[i], result.get(i)); + } + } + + + public void skiptestUnflatten() { + LangUtil.FlattenSpec COMMA = LangUtil.FlattenSpec.COMMA; + LangUtil.FlattenSpec LIST = LangUtil.FlattenSpec.LIST; + + FTest[] tests = new FTest[] + { new FTest("[]", new String[0], LIST) + , new FTest("[1]", new String[] {"1"}, LIST) + , new FTest("[1, 2]", new String[] {"1", "2"}, LIST) + , new FTest("[1,2]", new String[] {"1,2"}, LIST) + , new FTest("[1, 2, 3]", new String[] {"1","2","3"}, LIST) + }; + for (int i = 0; i < tests.length; i++) { + checkUnflatten(tests[i]); + } + } + + + public void testArrayList() { + ArrayList l = new ArrayList(); + l.add(null); + l.add(null); + assertTrue(null == l.get(0)); + assertTrue(null == l.get(1)); + assertTrue(2 == l.size()); + assertEquals("[null, null]", "" + l); + } + + public void testCombineStrings() { + String[] one = new String[]{}; + String[] two = new String[]{}; + String[] expect = new String[]{}; + checkCombineStrings(one, two, expect); + + one = new String[]{null}; + two = new String[]{null}; + expect = new String[]{}; + checkCombineStrings(one, two, expect); + + one = new String[]{"1"}; + two = new String[]{null}; + expect = new String[]{"1"}; + checkCombineStrings(one, two, expect); + + one = new String[]{null}; + two = new String[]{"2"}; + expect = new String[]{"2"}; + checkCombineStrings(one, two, expect); + + one = new String[]{"1"}; + two = new String[]{"2"}; + expect = new String[]{"1", "2"}; + checkCombineStrings(one, two, expect); + + one = new String[]{null, null, "1", null, null}; + two = new String[]{null, "2", null}; + expect = new String[]{"1", "2"}; + checkCombineStrings(one, two, expect); + + one = new String[]{"1", "2", "3", "4"}; + two = new String[]{"5", null, "6"}; + expect = new String[]{"1", "2", "3", "4", "5", "6"}; + checkCombineStrings(one, two, expect); + + } + void checkCombineStrings(String[] one, String[] two, String[] expected) { + String[] actual = LangUtil.combine(one, two); + String aString = LangUtil.arrayAsList(actual).toString(); + String eString = LangUtil.arrayAsList(expected).toString(); + String both = "actual=\"" + aString + "\" expected=\"" + eString + "\""; + assertTrue(both, aString.equals(eString)); + } + + final String[] sABCDE = new String[] {"A", "B", "C", "D", "E" }; + final String[] sABC = new String[] {"A", "B", "C" }; + final String[] sDE = new String[] {"D", "E" }; + final String[] sabcde = new String[] {"a", "b", "c", "d", "e" }; + final String[] sabc = new String[] {"a", "b", "c" }; + final String[] sde = new String[] {"d", "e" }; + final String[] s12345 = new String[] {"1", "2", "3", "4", "5" }; + final String[] s13579 = new String[] {"1", "3", "5", "7", "9" }; + final String[] s02468 = new String[] {"0", "2", "4", "6", "8" }; + final String[] s135 = new String[] {"1", "3", "5" }; + final String[] s79 = new String[] {"7", "9" }; + final String[] s24 = new String[] {"2", "4" }; + final String[] s0 = new String[] {"0"}; + final String[] s068 = new String[] {"0", "6", "8" }; + final boolean unmodifiable = true; + final boolean modifiable = false; + final List lABCDE = makeList(unmodifiable, sABCDE); + final List lABC = makeList(unmodifiable, sABC); + final List lDE = makeList(unmodifiable, sDE); + final List labcde = makeList(unmodifiable, sabcde); + final List labc = makeList(unmodifiable, sabc); + final List lde = makeList(unmodifiable, sde); + final List l12345 = makeList(unmodifiable, s12345); + final List l13579 = makeList(unmodifiable, s13579); + final List l02468 = makeList(unmodifiable, s02468); + final List l135 = makeList(unmodifiable, s135); + final List l79 = makeList(unmodifiable, s79); + final List l24 = makeList(unmodifiable, s24); + final List l0 = makeList(unmodifiable, s0); + final List l068 = makeList(unmodifiable, s068); + final List rlabcde = makeList(modifiable, sabcde); + final List rlabc = makeList(modifiable, sabc); + final List rlde = makeList(modifiable, sde); + final List rlABCDE = makeList(modifiable, sABCDE); + final List rlABC = makeList(modifiable, sABC); + final List rlDE = makeList(modifiable, sDE); + final List rl12345 = makeList(modifiable, s12345); + final List rl13579 = makeList(modifiable, s13579); + final List rl02468 = makeList(modifiable, s02468); + final List rl135 = makeList(modifiable, s135); + final List rl79 = makeList(modifiable, s79); + final List rl24 = makeList(modifiable, s24); + final List rl0 = makeList(modifiable, s0); + final List rl068 = makeList(modifiable, s068); + final List NONE = Collections.EMPTY_LIST; + { + Collections.shuffle(rlABCDE); + Collections.shuffle(rlABC); + Collections.shuffle(rlDE); + Collections.shuffle(rlabcde); + Collections.shuffle(rlabc); + Collections.shuffle(rlde); + Collections.shuffle(rl12345); + Collections.shuffle(rl13579); + Collections.shuffle(rl02468); + Collections.shuffle(rl135); + Collections.shuffle(rl79); + Collections.shuffle(rl24); + Collections.shuffle(rl0); + Collections.shuffle(rl068); + } + + + public void testDiffsEmptyIdentities() { + checkDiff(l02468, null, l02468, NONE); + checkDiff(null, l02468, NONE, l02468); + checkDiff(l0, null, l0, NONE); + checkDiff(null, l0, NONE, l0); + checkDiff(l0, rl0, NONE, NONE); + checkDiff(labc, rlabc, NONE, NONE); + } + + public void testDiffsEmpties() { + checkDiff(NONE, NONE, NONE, NONE); + checkDiff(null, NONE, NONE, NONE); + checkDiff(NONE, null, NONE, NONE); + checkDiff(null, null, NONE, NONE); + checkDiff(null, null, NONE, NONE); + } + + public void testDiffsIdentities() { + checkDiff(l02468, l02468, NONE, NONE); + checkDiff(rl02468, l02468, NONE, NONE); + checkDiff(l02468, rl02468, NONE, NONE); + checkDiff(l13579, l13579, NONE, NONE); + checkDiff(rl13579, l13579, NONE, NONE); + checkDiff(l13579, rl13579, NONE, NONE); + checkDiff(l13579, rl13579, NONE, NONE); + } + public void testDiffsEvens() { + checkDiff(l02468, l12345, l068, l135); + checkDiff(rl02468, rl12345, rl068, rl135); + } + + public void testDiffsOdds() { + checkDiff(l13579, l12345, l79, l24); + checkDiff(rl13579, rl12345, rl79, rl24); + checkDiff(l13579, rl12345, l79, rl24); + checkDiff(rl13579, l12345, rl79, l24); + } + + public void testSoftDiffs() { + checkDiffSoft(labcde, lABCDE, NONE, NONE); + checkDiffSoft(lABC, labc, NONE, NONE); + checkDiffSoft(lABCDE, lABC, lDE, NONE); + checkDiffSoft(lDE, lABCDE, NONE, lABC); + checkDiffSoft(rlABCDE, rlABC, rlDE, NONE); + checkDiffSoft(rlDE, rlABCDE, NONE, rlABC); + checkDiffSoft(labcde, lABC, lDE, NONE); + checkDiffSoft(lde, lABCDE, NONE, lABC); + checkDiffSoft(rlabcde, rlABC, rlDE, NONE); + checkDiffSoft(rlde, rlABCDE, NONE, rlABC); + } + + // ---------------------- utilities + List makeList(boolean unmodifiable, String[] ra) { + if (unmodifiable) { + return Collections.unmodifiableList(Arrays.asList(ra)); + } else { + ArrayList list = new ArrayList(); + list.addAll(Arrays.asList(ra)); + return list; + } + } + + /** check both hard and soft - assuming list contain String */ + void checkDiff(List expected, List actual, List missing, List extra) { + ArrayList extraOut = new ArrayList(); + ArrayList missingOut = new ArrayList(); + LangUtil.makeDiffs(expected, actual, missingOut, extraOut); + checkSame(missing, missingOut); + checkSame(extra, extraOut); + extraOut.clear(); + missingOut.clear(); + + LangUtil.makeSoftDiffs(expected, actual, missingOut, extraOut, + String.CASE_INSENSITIVE_ORDER); + checkSame(missing, missingOut); // XXX does not detect bad order + checkSame(extra, extraOut); + } + + void checkSame(Collection one, Collection two) { // just convert and string-compare? + String label = one + "?=" + two; + assertTrue(label, (null == one) == (null == two)); + if (null != one) { + assertTrue(label, one.containsAll(two)); + assertTrue(label, two.containsAll(one)); + } + } + + /** check only soft - assuming list contain String */ + void checkDiffSoft(List expected, List actual, List missing, List extra) { + ArrayList extraOut = new ArrayList(); + ArrayList missingOut = new ArrayList(); + LangUtil.makeSoftDiffs(expected, actual, missingOut, extraOut, + String.CASE_INSENSITIVE_ORDER); + checkSameSoft(missing, missingOut); + checkSameSoft(extra, extraOut); + } + + /** @param one modifiable List of String + * @param two modifiable List of String + */ + void checkSameSoft(List one, List two) { // assume String + String label = one + "?=" + two; + assertTrue(label, (null == one) == (null == two)); + if (null != one) { + ArrayList aone = new ArrayList(); + aone.addAll(one); + ArrayList atwo = new ArrayList(); + aone.addAll(two); + Collections.sort(aone); + Collections.sort(atwo); + String sone = (""+aone).toLowerCase(); + String stwo = (""+aone).toLowerCase(); + assertTrue(label, sone.equals(stwo)); + } + } + + static class FTest { + String toUnflatten; + String[] unflattened; + LangUtil.FlattenSpec spec; + FTest(String in, String[] out, LangUtil.FlattenSpec spec) { + toUnflatten = in; + unflattened = out; + this.spec = spec; + } + public String toString() { + return "FTest(" + + "toUnflatten=" + toUnflatten + + ", unflattened=" + Arrays.asList(unflattened) + + ", spec=" + spec + + ")"; + } + } + + +} diff --git a/testing/testsrc/org/aspectj/testing/util/MessageUtilTest.java b/testing/testsrc/org/aspectj/testing/util/MessageUtilTest.java new file mode 100644 index 000000000..26d8523dd --- /dev/null +++ b/testing/testsrc/org/aspectj/testing/util/MessageUtilTest.java @@ -0,0 +1,130 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +package org.aspectj.testing.util; + +import org.aspectj.bridge.IMessage; +import org.aspectj.bridge.ISourceLocation; +import org.aspectj.bridge.Message; +import org.aspectj.bridge.MessageHandler; +import org.aspectj.bridge.MessageUtil; +import org.aspectj.bridge.SourceLocation; + +import java.io.File; +import java.io.IOException; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +import junit.framework.TestCase; + +/** + * + */ +public class MessageUtilTest extends TestCase { + public MessageUtilTest(String s) { + super(s); + } + + MessageHandler samples; + List /* Exception */ exceptions; + List /*ISourceLocation*/ locations; + List /*String */ messageTexts; + + + public void testMessageRendering() { + MessageHandler messages = getSampleMessages(); + System.out.println("testMessageRendering(): run manually evaluate by inspection"); + PrintStream oldOut = System.out; + // comment to inspect manually + System.setOut(NullPrintStream.NULL_PrintStream); + try { + MessageUtil.print(System.out, messages, "all label -> ", MessageUtil.MESSAGE_LABEL, MessageUtil.PICK_ALL); + MessageUtil.print(System.out, messages, "info short -> ", MessageUtil.MESSAGE_SHORT, MessageUtil.PICK_INFO); + MessageUtil.print(System.out, messages, "fail line -> ", MessageUtil.MESSAGE_LINE, MessageUtil.PICK_FAIL); + MessageUtil.print(System.out, messages, "debug wide line -> ", MessageUtil.MESSAGE_WIDELINE, MessageUtil.PICK_DEBUG); + MessageUtil.print(System.out, messages, "warn no-loc label -> ", MessageUtil.MESSAGE_LABEL_NOLOC, MessageUtil.PICK_WARNING); + MessageUtil.print(System.out, messages, "abort force-loc line -> ", MessageUtil.MESSAGE_LINE_FORCE_LOC, MessageUtil.PICK_ABORT); + MessageUtil.print(System.out, messages, "info+ short -> ", MessageUtil.MESSAGE_SHORT, MessageUtil.PICK_INFO_PLUS); + MessageUtil.print(System.out, messages, "fail+ line -> ", MessageUtil.MESSAGE_LINE, MessageUtil.PICK_FAIL_PLUS); + MessageUtil.print(System.out, messages, "debug+ wide line -> ", MessageUtil.MESSAGE_WIDELINE, MessageUtil.PICK_DEBUG_PLUS); + MessageUtil.print(System.out, messages, "warn+ no-loc label -> ", MessageUtil.MESSAGE_LABEL_NOLOC, MessageUtil.PICK_WARNING_PLUS); + MessageUtil.print(System.out, messages, "abort+ force-loc line -> ", MessageUtil.MESSAGE_LINE_FORCE_LOC, MessageUtil.PICK_ABORT_PLUS); + } finally { + System.setOut(oldOut); + } + } + + + List getSampleMessageTexts() { + if (null == messageTexts) { + ArrayList result = new ArrayList(); + result.addAll(Arrays.asList(new String[] + { "one", "two", "now is the time for all good men..." })); + messageTexts = result; + } + return messageTexts; + } + + List getSampleExceptions() { + if (null == exceptions) { + ArrayList result = new ArrayList(); + int i = 1; + result.add(new Error("Error " + i++)); + result.add(new RuntimeException("RuntimeException " + i++)); + result.add(new IOException("IOException " + i++)); + exceptions = result; + } + return exceptions; + } + + List getSampleLocations() { + if (null == locations) { + ArrayList result = new ArrayList(); + File file = new File("testsrc/org/aspectj/testing/util/MessageUtilTest.java"); + result.add(new SourceLocation(file, 1, 2, 1)); + result.add(new SourceLocation(file, 100, 100, 0)); + locations = result; + } + return locations; + } + + MessageHandler getSampleMessages() { + MessageHandler result = new MessageHandler(); + for (Iterator kinds = IMessage.KINDS.iterator(); kinds.hasNext();) { + IMessage.Kind kind = (IMessage.Kind) kinds.next(); + for (Iterator locs = getSampleLocations().iterator(); locs.hasNext();) { + ISourceLocation sourceLoc = (ISourceLocation) locs.next(); + for (Iterator texts = getSampleMessageTexts().iterator(); + texts.hasNext(); + ) { + String text = (String) texts.next(); + for (Iterator exs = getSampleExceptions().iterator(); + exs.hasNext(); + ) { + Throwable thrown = (Throwable) exs.next(); + result.handleMessage(new Message(text, kind, thrown, sourceLoc)); + } + result.handleMessage(new Message(text, kind, null, sourceLoc)); + } + result.handleMessage(new Message("", kind, null, sourceLoc)); + } + result.handleMessage(new Message("", kind, null, null)); + } + return result; + } + +} diff --git a/testing/testsrc/org/aspectj/testing/util/StreamGrabberTest.java b/testing/testsrc/org/aspectj/testing/util/StreamGrabberTest.java new file mode 100644 index 000000000..9cd882fa8 --- /dev/null +++ b/testing/testsrc/org/aspectj/testing/util/StreamGrabberTest.java @@ -0,0 +1,112 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +/* + * StreamGrabberTest.java created on May 16, 2002 + * + */ +package org.aspectj.testing.util; + + +import java.io.PrintStream; + +import junit.framework.TestCase; +import junit.textui.TestRunner; + +/** + * + * @author isberg + */ +public class StreamGrabberTest extends TestCase { + + + private static final String ME + = "org.aspectj.testing.util.StreamGrabberTest"; + + /** @param args ignored */ + public static void main(String[] args) { + TestRunner.main(new String[] {ME}); + } + + public StreamGrabberTest(String s) { super(s); } + + public void testHide() { + PrintStream restore = System.out; + System.setOut(new PrintStream(NullPrintStream.NULL_OutputStream)); + System.out.println("OutputStream should not print!!!!!!!!!!!!!!!!!!!"); + System.setOut(new PrintStream(NullPrintStream.NULL_PrintStream)); + System.out.println("PrintStream should not print!!!!!!!!!!!!!!!!!!!"); + System.setOut(restore); + } + + /** + * Test StreamSniffer by setting up a delegate System.out + * and a normal System.out (delegating to System.out) + * and verifying that both get the same result. + */ + public void testGrab() { + StringBuffer delegate = new StringBuffer(); + StreamSniffer out = new StreamSniffer(System.out); + out.setBuffer(delegate); + System.setOut(new PrintStream(out)); + StreamSniffer g = new StreamSniffer(System.out); + System.setOut(new PrintStream(g)); + StringBuffer buf = new StringBuffer(); + g.setBuffer(buf); + + printLoop("f", buf, delegate); + printLoop("now is the time for all good men...", buf, delegate); + printlnLoop("f", buf, delegate); + printlnLoop("now is the time for all good men...", buf, delegate); + } + + private void printLoop(String expect, StringBuffer buf, StringBuffer delegate) { + System.out.print(expect); + String actual = buf.toString(); + String delegateActual = delegate.toString(); + assertTrue(expect + "=" + actual, expect.equals(actual)); + assertTrue(expect + "=" + delegateActual, expect.equals(delegateActual)); + buf.setLength(0); + delegate.setLength(0); + System.out.print(expect); + + actual = buf.toString(); + delegateActual = delegate.toString(); + assertTrue(expect + "=" + actual, expect.equals(actual)); + assertTrue(expect + "=" + delegateActual, expect.equals(delegateActual)); + buf.setLength(0); + delegate.setLength(0); + } + + private void printlnLoop(String expect, StringBuffer buf, StringBuffer delegate) { + // copy/paste of printLoop, using println + expect = expect.trim(); + System.out.println(expect); + String actual = buf.toString().trim(); + String delegateActual = delegate.toString().trim(); + assertTrue(expect + "=" + actual, expect.equals(actual)); + assertTrue(expect + "=" + delegateActual, expect.equals(delegateActual)); + buf.setLength(0); + delegate.setLength(0); + + System.out.println(expect); + actual = buf.toString().trim(); + delegateActual = delegate.toString().trim(); + assertTrue(expect + "=" + actual, expect.equals(actual)); + assertTrue(expect + "=" + delegateActual, expect.equals(delegateActual)); + buf.setLength(0); + delegate.setLength(0); + } + +} diff --git a/testing/testsrc/org/aspectj/testing/util/TestDiffsTest.java b/testing/testsrc/org/aspectj/testing/util/TestDiffsTest.java new file mode 100644 index 000000000..4ce7792c6 --- /dev/null +++ b/testing/testsrc/org/aspectj/testing/util/TestDiffsTest.java @@ -0,0 +1,114 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.util; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.ListIterator; + +import junit.framework.TestCase; +import org.aspectj.util.FileUtil; + +/** + * + */ +public class TestDiffsTest extends TestCase { + + /** + * Expected results in test below. + */ + private static void genTestInput(File expected, File actual) throws IOException { + FileWriter writer = null; + try { + writer = new FileWriter(expected); + PrintWriter pw = new PrintWriter(writer); + pw.println("PASS passed in both"); + pw.println("## random text to ignore: " + System.currentTimeMillis()); + pw.println("FAIL failed in both"); + pw.println("PASS failed in actual (broken)"); + pw.println("FAIL passed in actual (fixed)"); + pw.println("PASS not in actual (missing-pass)"); + pw.println("FAIL not in actual (missing-fail)"); + pw.flush(); + writer.close(); + + writer = new FileWriter(actual); + pw = new PrintWriter(writer); + pw.println("PASS passed in actual (fixed)"); + pw.println("## random text to ignore: " + System.currentTimeMillis()); + pw.println("PASS not in expected (added-pass)"); + pw.println("FAIL failed in both"); + pw.println("PASS passed in both"); + pw.println("FAIL failed in actual (broken)"); + pw.println("FAIL not in expected (added-fail)"); + pw.flush(); + writer.close(); + writer = null; + } finally { + if (null != writer) { + try { writer.close(); } + catch (IOException e) { } // ignore + } + } + } + + ArrayList tempFiles; + /** + * Constructor for FileUtilTest. + * @param arg0 + */ + public TestDiffsTest(String arg0) { + super(arg0); + tempFiles = new ArrayList(); + } + + public void tearDown() { + for (ListIterator iter = tempFiles.listIterator(); iter.hasNext();) { + File dir = (File) iter.next(); + FileUtil.deleteContents(dir); + dir.delete(); + iter.remove(); + } + } + + + public void testCompareResults() { + File tempDir = org.aspectj.util.FileUtil.getTempDir("testCompareResults"); + File expected = new File(tempDir, "expected.txt"); + File actual = new File(tempDir, "actual.txt"); + tempFiles.add(expected); + tempFiles.add(actual); + try { + genTestInput(expected, actual); + } catch (IOException e) { + assertTrue(e.getMessage(), false); + } + TestDiffs result = TestDiffs.compareResults(expected, actual); + assertEquals(2, result.missing.size()); + assertEquals(2, result.added.size()); + assertEquals(1, result.fixed.size()); + assertEquals(1, result.broken.size()); + assertEquals(3, result.actualFailed.size()); + assertEquals(3, result.actualPassed.size()); + assertEquals(6, result.actual.size()); + assertEquals(3, result.expectedFailed.size()); + assertEquals(3, result.expectedPassed.size()); + assertEquals(6, result.expected.size()); + assertEquals(1, result.stillFailing.size()); + assertEquals(1, result.stillPassing.size()); + } +} diff --git a/testing/testsrc/org/aspectj/testing/util/UtilTests.java b/testing/testsrc/org/aspectj/testing/util/UtilTests.java new file mode 100644 index 000000000..3ec14d7df --- /dev/null +++ b/testing/testsrc/org/aspectj/testing/util/UtilTests.java @@ -0,0 +1,36 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +package org.aspectj.testing.util; + +import junit.framework.*; + +public class UtilTests extends TestCase { + + public static Test suite() { + TestSuite suite = new TestSuite(UtilTests.class.getName()); + //$JUnit-BEGIN$ + suite.addTestSuite(BridgeUtilTest.class); + suite.addTestSuite(FileUtilTest.class); + suite.addTestSuite(IteratorWrapperTest.class); + suite.addTestSuite(LangUtilTest.class); + suite.addTestSuite(MessageUtilTest.class); + suite.addTestSuite(StreamGrabberTest.class); + //$JUnit-END$ + return suite; + } + + public UtilTests(String name) { super(name); } + +} diff --git a/testing/testsrc/org/aspectj/testing/xml/AjcSpecXmlReaderTest.java b/testing/testsrc/org/aspectj/testing/xml/AjcSpecXmlReaderTest.java new file mode 100644 index 000000000..157cf2dba --- /dev/null +++ b/testing/testsrc/org/aspectj/testing/xml/AjcSpecXmlReaderTest.java @@ -0,0 +1,236 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.xml; + +import org.aspectj.bridge.MessageHandler; +import org.aspectj.bridge.MessageUtil; +import org.aspectj.testing.harness.bridge.AjcSpecTest; +import org.aspectj.testing.harness.bridge.CompilerRun; +import org.aspectj.testing.harness.bridge.FlatSuiteReader; +import org.aspectj.testing.harness.bridge.AjcTest; +import org.aspectj.testing.run.IRunStatus; +import org.aspectj.testing.run.IRunValidator; +import org.aspectj.testing.run.RunValidator; +import org.aspectj.util.LangUtil; + +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import junit.framework.TestCase; + +/** + * + */ +public class AjcSpecXmlReaderTest extends TestCase { + + ArrayList tempFiles = new ArrayList(); + /** + * Constructor for AjcSpecXmlReaderTest. + * @param name + */ + public AjcSpecXmlReaderTest(String name) { + super(name); + } + + public void setUp() { + tempFiles.clear(); + //System.out.println("XXX test requires compiler and bridgeImpl projects on classpath"); + } + + public void tearDown() { + if (!LangUtil.isEmpty(tempFiles)) { + for (Iterator iter = tempFiles.iterator(); iter.hasNext();) { + File file = (File) iter.next(); + if (file.canRead()) { + file.delete(); + } + } + } + } + + /** test that all AjcSpecXmlReader.me.expectedProperties() are bean-writable */ + public void testBeanInfo() throws IntrospectionException { + AjcSpecXmlReader me = AjcSpecXmlReader.getReader(); + AjcSpecXmlReader.BProps[] expected = me.expectedProperties(); + PropertyDescriptor[] des; + for (int i = 0; i < expected.length; i++) { + Class clazz = expected[i].cl; + BeanInfo beanInfo = Introspector.getBeanInfo(clazz); + assertTrue(null != beanInfo); + des = beanInfo.getPropertyDescriptors(); + for (int j = 0; j < expected[i].props.length; j++) { + String name = expected[i].props[j]; + String fqn = clazz.getName() + "." + name; + boolean gotIt = false; + for (int k = 0; k < des.length; k++) { + String desName = des[k].getName(); + if (name.equals(desName)) { + assertTrue(fqn, null != des[k].getWriteMethod()); + gotIt = true; + } + } + assertTrue("no such property: " + fqn, gotIt); + } + } + + } + public void testAjcTests() throws IOException { + checkXmlRoundTrip("../tests/ajcTests"); + } + + public void testAjcTests10() throws IOException { + checkXmlRoundTrip("../tests/ajcTests10"); + } + + public void testAjcTestsBroken() throws IOException { + checkXmlRoundTrip("../tests/ajcTestsBroken"); + } + + public void testAjcTestsAttic() throws IOException { + checkXmlRoundTrip("../tests/ajcTestsAttic"); + } + + public void testAjcHarnessTests() throws IOException { + checkXmlRoundTrip("../tests/ajcHarnessTests"); + } + + void checkXmlRoundTrip(String path) throws IOException { + String xmlPath = path + ".xml"; + String xml2Path = path + ".tmp.xml"; + + final File file1 = new File(xmlPath); + final ArrayList toDelete = new ArrayList(); + final AjcSpecXmlReader writer = AjcSpecXmlReader.getReader(); + + assertTrue("" + file1, file1.canRead()); + AjcTest.Suite.Spec suite1 = writer.readAjcSuite(file1); + assertNotNull(suite1); + + File file2 = new File(xml2Path); + String warning = writer.writeSuiteToXmlFile(file2, suite1); + toDelete.add(file2); + assertTrue(warning, null == warning); + + AjcTest.Suite.Spec suite2 = writer.readAjcSuite(file1); + assertNotNull(suite2); + AjcSpecTest.sameAjcSuiteSpec(suite1, suite2, this); + + for (Iterator iter = toDelete.iterator(); iter.hasNext();) { + ((File) iter.next()).delete(); + } + } + + void checkRoundTrip(String path) throws IOException, Exception { + // XXX once .txt gone, add bugId and keywords to test + String txtPath = path + ".txt"; + String xmlPath = path + ".tmp.xml"; + String xml2Path = path + ".tmp2.xml"; + + // read flat, write the xml variant, read back, and compare + AjcSpecXmlReader writer = AjcSpecXmlReader.getReader(); + File file0 = new File(txtPath); + File file1 = new File(xmlPath); + ArrayList toDelete = new ArrayList(); + AjcTest.Suite.Spec suite0 = null; + if (file0.canRead()) { + System.out.println("reading " + file0); + suite0 = FlatSuiteReader.ME.readSuite(file0); + String warning = writer.writeSuiteToXmlFile(file1, suite0); + toDelete.add(file1); + assertTrue(warning, null == warning); + } else { + file1 = new File(path + ".xml"); + if (file1.canRead()) { + System.out.println("reading " + file1); + suite0 = writer.readAjcSuite(file1); + } else { + System.err.println("Skipping as not in module: " + file0); + return; + } + } + assertNotNull(suite0); + + //System.err.println("----------------------- suite0 " + txtPath); + //suite0.printAll(System.err, ""); + assertTrue("" + file1, file1.canRead()); + System.out.println("reading " + file1); + AjcTest.Suite.Spec suite1 = writer.readAjcSuite(file1); + assertNotNull(suite1); + //System.err.println("----------------------- suite1 " + xmlPath); + //suite0.printAll(System.err, ""); + AjcSpecTest.sameAjcSuiteSpec(suite0, suite1, this); + + // same for second-generation xml + file1 = new File(xml2Path); + String warning = writer.writeSuiteToXmlFile(file1, suite1); + toDelete.add(file1); + // XXX enable later assertTrue(warning, null == warning); + if (null != warning) { + System.out.println("warning " + file1 + ": " + warning); + } + System.out.println("reading " + file1); + AjcTest.Suite.Spec suite2 = writer.readAjcSuite(file1); + assertNotNull(suite2); + //System.err.println("----------------------- suite2 " + xml2Path); + AjcSpecTest.sameAjcSuiteSpec(suite1, suite2, this); + AjcSpecTest.sameAjcSuiteSpec(suite0, suite2, this); + + for (Iterator iter = toDelete.iterator(); iter.hasNext();) { + ((File) iter.next()).delete(); + } + } +} + + // ------------------- XXX retry execution round-trips when eclipse working + //AjcSpecTest.sameAjcTestSpec(txtList, xmlSpec, this); + +// List xmlList = writer.readAjcTests(xmlFile); +// AjcSpecTest.sameAjcTestLists(txtList, xmlList, this); +// List xml2List = writer.readAjcTests(xmlFile); +// AjcSpecTest.sameAjcTestLists(xmlList, xml2List, this); + + + // ------------------ now run them both and compare results +// // run the flat and xml variants, then compare +// MessageHandler xmlHandler = new MessageHandler(); +// IRunStatus xmlStatus = runFile(xmlPath, xmlHandler); +// MessageHandler txtHandler = new MessageHandler(); +// IRunStatus txtStatus = runFile(txtPath, txtHandler); +// +// +// // both should pass or fail.. +// IRunValidator v = RunValidator.NORMAL; +// boolean xmlPassed = v.runPassed(xmlStatus); +// boolean txtPassed = v.runPassed(txtStatus); +// boolean passed = (xmlPassed == txtPassed); +// if (!xmlPassed) { +// MessageUtil.print(System.err, xmlStatus); +// } +// if (!txtPassed) { +// MessageUtil.print(System.err, txtStatus); +// } +// if (!passed) { // calculate diffs +// } +// IRunStatus runFile(String path, MessageHandler handler) throws IOException { +// Main runner = new Main(new String[] {path}, handler); +// String id = "AjcSpecXmlReaderTest.runFile(" + path + ")"; +// return runner.runMain(id, handler, System.err); +// } diff --git a/testing/testsrc/org/aspectj/testing/xml/TestingXmlTests.java b/testing/testsrc/org/aspectj/testing/xml/TestingXmlTests.java new file mode 100644 index 000000000..fbefdba5d --- /dev/null +++ b/testing/testsrc/org/aspectj/testing/xml/TestingXmlTests.java @@ -0,0 +1,31 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +package org.aspectj.testing.xml; + +import junit.framework.*; + +public class TestingXmlTests extends TestCase { + + public static Test suite() { + TestSuite suite = new TestSuite(TestingXmlTests.class.getName()); + //$JUnit-BEGIN$ + suite.addTestSuite(AjcSpecXmlReaderTest.class); + //$JUnit-END$ + return suite; + } + + public TestingXmlTests(String name) { super(name); } + +} diff --git a/testing/testsrc/org/aspectj/testing/xml/XMLWriterTest.java b/testing/testsrc/org/aspectj/testing/xml/XMLWriterTest.java new file mode 100644 index 000000000..80790773e --- /dev/null +++ b/testing/testsrc/org/aspectj/testing/xml/XMLWriterTest.java @@ -0,0 +1,44 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.xml; + +import java.util.Arrays; + +import junit.framework.TestCase; + +/** + * + */ +public class XMLWriterTest extends TestCase { + + public XMLWriterTest(String name) { + super(name); + } + + /** @see LangUtilTest#testCommaSplit() */ + public void testUnflattenList() { + checkUnflattenList("", new String[] {""}); + checkUnflattenList("1", new String[] {"1"}); + checkUnflattenList(" 1 2 ", new String[] {"1 2"}); + checkUnflattenList(" 1 , 2 ", new String[] {"1", "2"}); + checkUnflattenList("1,2,3,4", new String[] {"1", "2", "3", "4"}); + } + + void checkUnflattenList(String input, String[] expected) { + String[] actual = XMLWriter.unflattenList(input); + String a = "" + Arrays.asList(actual); + String e = "" + Arrays.asList(expected); + assertTrue(e + "==" + a, e.equals(a)); + } +} -- 2.39.5
  • build: bootstrap libraries for the build process.
  • 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

    ZMpCV~}3g>Bp$@UU3NF4VuK=-rV_k2JO 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≦OKxm}ny>@OqW`DV#3GnFOR4Ul%5 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