aoqi@0: /* aoqi@0: * Copyright (c) 2013 Oracle and/or its affiliates. All rights reserved. aoqi@0: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. aoqi@0: * aoqi@0: * This code is free software; you can redistribute it and/or modify it aoqi@0: * under the terms of the GNU General Public License version 2 only, as aoqi@0: * published by the Free Software Foundation. aoqi@0: * aoqi@0: * This code is distributed in the hope that it will be useful, but WITHOUT aoqi@0: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or aoqi@0: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License aoqi@0: * version 2 for more details (a copy is included in the LICENSE file that aoqi@0: * accompanied this code). aoqi@0: * aoqi@0: * You should have received a copy of the GNU General Public License version aoqi@0: * 2 along with this work; if not, write to the Free Software Foundation, aoqi@0: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. aoqi@0: * aoqi@0: * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA aoqi@0: * or visit www.oracle.com if you need additional information or have any aoqi@0: * questions. aoqi@0: */ aoqi@0: aoqi@0: /* aoqi@0: * @test aoqi@0: * @bug 8005085 8008762 8008751 8013065 8015323 8015257 aoqi@0: * @summary Type annotations on anonymous and inner class. aoqi@0: * Six TYPE_USE annotations are repeated(or not); Four combinations create aoqi@0: * four test files, and each results in the test class and 2 anonymous classes. aoqi@0: * Each element of these three classes is checked for expected number of the aoqi@0: * four annotation Attributes. Expected annotation counts depend on type of aoqi@0: * annotation place on type of element (a FIELD&TYPE_USE element on a field aoqi@0: * results in 2). Elements with no annotations expect 0. aoqi@0: * Source template is read in from testanoninner.template aoqi@0: * aoqi@0: */ aoqi@0: import java.lang.annotation.*; aoqi@0: import java.io.*; aoqi@0: import java.util.List; aoqi@0: import java.util.LinkedList; aoqi@0: import com.sun.tools.classfile.*; aoqi@0: import java.nio.file.Files; aoqi@0: import java.nio.charset.*; aoqi@0: import java.io.File; aoqi@0: import java.io.IOException; aoqi@0: aoqi@0: aoqi@0: import java.lang.annotation.*; aoqi@0: import static java.lang.annotation.RetentionPolicy.*; aoqi@0: import static java.lang.annotation.ElementType.*; aoqi@0: aoqi@0: /* aoqi@0: * A source template is read in and testname and annotations are inserted aoqi@0: * via replace(). aoqi@0: */ aoqi@0: public class TestAnonInnerClasses extends ClassfileTestHelper { aoqi@0: // tally errors and test cases aoqi@0: int errors = 0; aoqi@0: int checks = 0; aoqi@0: //Note expected test count in case of skips due to bugs. aoqi@0: int tc = 0, xtc = 180; // 45 x 4 variations of repeated annotations. aoqi@0: File testSrc = new File(System.getProperty("test.src")); aoqi@0: aoqi@0: String[] AnnoAttributes = { aoqi@0: Attribute.RuntimeVisibleTypeAnnotations, aoqi@0: Attribute.RuntimeInvisibleTypeAnnotations, aoqi@0: Attribute.RuntimeVisibleAnnotations, aoqi@0: Attribute.RuntimeInvisibleAnnotations aoqi@0: }; aoqi@0: aoqi@0: // template for source files aoqi@0: String srcTemplate = "testanoninner.template"; aoqi@0: aoqi@0: // Four test files generated based on combinations of repeating annotations. aoqi@0: Boolean As= false, Bs=true, Cs=false, Ds=false, TAs=false,TBs=false; aoqi@0: Boolean[][] bRepeat = new Boolean[][]{ aoqi@0: /* no repeats */ {false, false, false, false, false, false}, aoqi@0: /* repeat A,C,TA */ {true, false, true, false, true, false}, aoqi@0: /* repeat B,D,TB */ {false, true, false, true, false, true}, aoqi@0: /* repeat all */ {true, true, true, true, true, true} aoqi@0: }; aoqi@0: // Save descriptions of failed test case; does not terminate upon a failure. aoqi@0: List failed = new LinkedList<>(); aoqi@0: aoqi@0: public static void main(String[] args) throws Exception { aoqi@0: new TestAnonInnerClasses().run(); aoqi@0: } aoqi@0: aoqi@0: // Check annotation counts and make reports sufficiently descriptive to aoqi@0: // easily diagnose. aoqi@0: void check(String testcase, int vtaX, int itaX, int vaX, int iaX, aoqi@0: int vtaA, int itaA, int vaA, int iaA) { aoqi@0: aoqi@0: String descr = " checking " + testcase+" _TYPE_, expected: " + aoqi@0: vtaX + ", " + itaX + ", " + vaX + ", " + iaX + "; actual: " + aoqi@0: vtaA + ", " + itaA + ", " + vaA + ", " + iaA; aoqi@0: String description; aoqi@0: description=descr.replace("_TYPE_","RuntimeVisibleTypeAnnotations"); aoqi@0: if (vtaX != vtaA) { aoqi@0: errors++; aoqi@0: failed.add(++checks + " " + testcase + ": (vtaX) " + vtaX + aoqi@0: " != " + vtaA + " (vtaA)"); aoqi@0: println(checks + " FAIL: " + description); aoqi@0: } else { aoqi@0: println(++checks + " PASS: " + description); aoqi@0: } aoqi@0: description=descr.replace("_TYPE_","RuntimeInvisibleTypeAnnotations"); aoqi@0: if (itaX != itaA) { aoqi@0: errors++; aoqi@0: failed.add(++checks + " " + testcase + ": (itaX) " + itaX + " != " + aoqi@0: itaA + " (itaA)"); aoqi@0: println(checks + " FAIL: " + description); aoqi@0: } else { aoqi@0: println(++checks + " PASS: " + description); aoqi@0: } aoqi@0: description=descr.replace("_TYPE_","RuntimeVisibleAnnotations"); aoqi@0: if (vaX != vaA) { aoqi@0: errors++; aoqi@0: failed.add(++checks + " " + testcase + ": (vaX) " + vaX + " != " + aoqi@0: vaA + " (vaA)"); aoqi@0: println(checks + " FAIL: " + description); aoqi@0: } else { aoqi@0: println(++checks + " PASS: " + description); aoqi@0: } aoqi@0: description=descr.replace("_TYPE_","RuntimeInvisibleAnnotations"); aoqi@0: if (iaX != iaA) { aoqi@0: errors++; aoqi@0: failed.add(++checks + " " + testcase + ": (iaX) " + iaX + " != " + aoqi@0: iaA + " (iaA)"); aoqi@0: println(checks + " FAIL: " + description); aoqi@0: } else { aoqi@0: println(++checks + " PASS: " + description); aoqi@0: } aoqi@0: println(""); aoqi@0: } aoqi@0: aoqi@0: // Print failed cases (if any) and throw exception for fail. aoqi@0: void report() { aoqi@0: if (errors!=0) { aoqi@0: System.err.println("Failed tests: " + errors + aoqi@0: "\nfailed test cases:\n"); aoqi@0: for (String t: failed) System.err.println(" " + t); aoqi@0: throw new RuntimeException("FAIL: There were test failures."); aoqi@0: } else aoqi@0: System.out.println("PASSED all tests."); aoqi@0: } aoqi@0: aoqi@0: void test(String ttype, ClassFile cf, Method m, Field f, boolean visible) { aoqi@0: int vtaActual = 0, aoqi@0: itaActual = 0, aoqi@0: vaActual = 0, aoqi@0: iaActual = 0, aoqi@0: vtaExp = 0, aoqi@0: itaExp = 0, aoqi@0: vaExp = 0, aoqi@0: iaExp = 0, aoqi@0: index = 0, aoqi@0: index2 = 0; aoqi@0: String memberName = null, aoqi@0: testcase = "undefined", aoqi@0: testClassName = null; aoqi@0: Attribute attr = null, aoqi@0: cattr = null; aoqi@0: Code_attribute CAttr = null; aoqi@0: // Get counts of 4 annotation Attributes on element being checked. aoqi@0: for (String AnnoType : AnnoAttributes) { aoqi@0: try { aoqi@0: switch (ttype) { aoqi@0: case "METHOD": aoqi@0: index = m.attributes.getIndex(cf.constant_pool, aoqi@0: AnnoType); aoqi@0: memberName = m.getName(cf.constant_pool); aoqi@0: if (index != -1) aoqi@0: attr = m.attributes.get(index); aoqi@0: //fetch index annotations from code attribute. aoqi@0: index2 = m.attributes.getIndex(cf.constant_pool, aoqi@0: Attribute.Code); aoqi@0: if (index2 != -1) { aoqi@0: cattr = m.attributes.get(index2); aoqi@0: assert cattr instanceof Code_attribute; aoqi@0: CAttr = (Code_attribute)cattr; aoqi@0: index2 = CAttr.attributes.getIndex(cf.constant_pool, aoqi@0: AnnoType); aoqi@0: if (index2 != -1) aoqi@0: cattr = CAttr.attributes.get(index2); aoqi@0: } aoqi@0: break; aoqi@0: case "FIELD": aoqi@0: index = f.attributes.getIndex(cf.constant_pool, aoqi@0: AnnoType); aoqi@0: memberName = f.getName(cf.constant_pool); aoqi@0: if (index != -1) aoqi@0: attr = f.attributes.get(index); aoqi@0: //fetch index annotations from code attribute. aoqi@0: index2 = cf.attributes.getIndex(cf.constant_pool, aoqi@0: Attribute.Code); aoqi@0: if (index2!= -1) { aoqi@0: cattr = cf.attributes.get(index2); aoqi@0: assert cattr instanceof Code_attribute; aoqi@0: CAttr = (Code_attribute)cattr; aoqi@0: index2 = CAttr.attributes.getIndex(cf.constant_pool, aoqi@0: AnnoType); aoqi@0: if (index2!= -1) aoqi@0: cattr = CAttr.attributes.get(index2); aoqi@0: } aoqi@0: break; aoqi@0: aoqi@0: default: aoqi@0: memberName = cf.getName(); aoqi@0: index = cf.attributes.getIndex(cf.constant_pool, aoqi@0: AnnoType); aoqi@0: if (index!= -1) attr = cf.attributes.get(index); aoqi@0: break; aoqi@0: } aoqi@0: } aoqi@0: catch (ConstantPoolException cpe) { cpe.printStackTrace(); } aoqi@0: try { aoqi@0: testClassName=cf.getName(); aoqi@0: testcase = ttype + ": " + testClassName + ": " + aoqi@0: memberName + ", "; aoqi@0: } aoqi@0: catch (ConstantPoolException cpe) { cpe.printStackTrace(); } aoqi@0: if (index != -1) { aoqi@0: switch (AnnoType) { aoqi@0: case Attribute.RuntimeVisibleTypeAnnotations: aoqi@0: //count RuntimeVisibleTypeAnnotations aoqi@0: RuntimeVisibleTypeAnnotations_attribute RVTAa = aoqi@0: (RuntimeVisibleTypeAnnotations_attribute)attr; aoqi@0: vtaActual += RVTAa.annotations.length; aoqi@0: break; aoqi@0: case Attribute.RuntimeVisibleAnnotations: aoqi@0: //count RuntimeVisibleAnnotations aoqi@0: RuntimeVisibleAnnotations_attribute RVAa = aoqi@0: (RuntimeVisibleAnnotations_attribute)attr; aoqi@0: vaActual += RVAa.annotations.length; aoqi@0: break; aoqi@0: case Attribute.RuntimeInvisibleTypeAnnotations: aoqi@0: //count RuntimeInvisibleTypeAnnotations aoqi@0: RuntimeInvisibleTypeAnnotations_attribute RITAa = aoqi@0: (RuntimeInvisibleTypeAnnotations_attribute)attr; aoqi@0: itaActual += RITAa.annotations.length; aoqi@0: break; aoqi@0: case Attribute.RuntimeInvisibleAnnotations: aoqi@0: //count RuntimeInvisibleAnnotations aoqi@0: RuntimeInvisibleAnnotations_attribute RIAa = aoqi@0: (RuntimeInvisibleAnnotations_attribute)attr; aoqi@0: iaActual += RIAa.annotations.length; aoqi@0: break; aoqi@0: } aoqi@0: } aoqi@0: // annotations from code attribute. aoqi@0: if (index2 != -1) { aoqi@0: switch (AnnoType) { aoqi@0: case Attribute.RuntimeVisibleTypeAnnotations: aoqi@0: //count RuntimeVisibleTypeAnnotations aoqi@0: RuntimeVisibleTypeAnnotations_attribute RVTAa = aoqi@0: (RuntimeVisibleTypeAnnotations_attribute)cattr; aoqi@0: vtaActual += RVTAa.annotations.length; aoqi@0: break; aoqi@0: case Attribute.RuntimeVisibleAnnotations: aoqi@0: //count RuntimeVisibleAnnotations aoqi@0: RuntimeVisibleAnnotations_attribute RVAa = aoqi@0: (RuntimeVisibleAnnotations_attribute)cattr; aoqi@0: vaActual += RVAa.annotations.length; aoqi@0: break; aoqi@0: case Attribute.RuntimeInvisibleTypeAnnotations: aoqi@0: //count RuntimeInvisibleTypeAnnotations aoqi@0: RuntimeInvisibleTypeAnnotations_attribute RITAa = aoqi@0: (RuntimeInvisibleTypeAnnotations_attribute)cattr; aoqi@0: itaActual += RITAa.annotations.length; aoqi@0: break; aoqi@0: case Attribute.RuntimeInvisibleAnnotations: aoqi@0: //count RuntimeInvisibleAnnotations aoqi@0: RuntimeInvisibleAnnotations_attribute RIAa = aoqi@0: (RuntimeInvisibleAnnotations_attribute)cattr; aoqi@0: iaActual += RIAa.annotations.length; aoqi@0: break; aoqi@0: } aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: switch (memberName) { aoqi@0: //METHODs aoqi@0: case "test" : vtaExp=4; itaExp=4; vaExp=0; iaExp=0; tc++; break; aoqi@0: case "mtest": vtaExp=4; itaExp=4; vaExp=1; iaExp=1; tc++; break; aoqi@0: case "m1": vtaExp=2; itaExp=2; vaExp=1; iaExp=1; tc++; break; aoqi@0: case "m2": vtaExp=4; itaExp=4; vaExp=1; iaExp=1; tc++; break; aoqi@0: case "m3": vtaExp=10; itaExp=10; vaExp=1; iaExp=1; tc++; break; aoqi@0: case "tm": vtaExp=6; itaExp=6; vaExp=1; iaExp=1; tc++; break; aoqi@0: //inner class aoqi@0: case "i_m1": vtaExp=2; itaExp=2; vaExp=1; iaExp=1; tc++; break; aoqi@0: case "i_m2": vtaExp=4; itaExp=4; vaExp=1; iaExp=1; tc++; break; aoqi@0: case "i_um": vtaExp=6; itaExp=6; vaExp=1; iaExp=1; tc++; break; aoqi@0: //local class aoqi@0: case "l_m1": vtaExp=2; itaExp=2; vaExp=1; iaExp=1; tc++; break; aoqi@0: case "l_m2": vtaExp=4; itaExp=4; vaExp=1; iaExp=1; tc++; break; aoqi@0: case "l_um": vtaExp=6; itaExp=6; vaExp=1; iaExp=1; tc++; break; aoqi@0: //anon class aoqi@0: case "mm_m1": vtaExp=2; itaExp=2; vaExp=1; iaExp=1; tc++; break; aoqi@0: case "mm_m2": vtaExp=4; itaExp=4; vaExp=1; iaExp=1; tc++; break; aoqi@0: case "mm_m3": vtaExp=10; itaExp=10;vaExp=1; iaExp=1; tc++; break; aoqi@0: case "mm_tm": vtaExp=6; itaExp=6; vaExp=1; iaExp=1; tc++; break; aoqi@0: //InnerAnon class aoqi@0: case "ia_m1": vtaExp=2; itaExp=2; vaExp=1; iaExp=1; tc++; break; aoqi@0: case "ia_m2": vtaExp=4; itaExp=4; vaExp=1; iaExp=1; tc++; break; aoqi@0: case "ia_um": vtaExp=6; itaExp=6; vaExp=1; iaExp=1; tc++; break; aoqi@0: //FIELDs aoqi@0: case "data": vtaExp = 2; itaExp=2; vaExp=1; iaExp=1; tc++; break; aoqi@0: case "odata1": vtaExp = 2; itaExp=2; vaExp=1; iaExp=1; tc++; break; aoqi@0: case "pdata1": vtaExp = 2; itaExp=2; vaExp=1; iaExp=1; tc++; break; aoqi@0: case "tdata": vtaExp = 2; itaExp=2; vaExp=1; iaExp=1; tc++; break; aoqi@0: case "sa1": vtaExp = 6; itaExp=6; vaExp=1; iaExp=1; tc++; break; aoqi@0: //inner class aoqi@0: case "i_odata1": vtaExp=2; itaExp=2; vaExp=1; iaExp=1; tc++; break; aoqi@0: case "i_pdata1": vtaExp=2; itaExp=2; vaExp=1; iaExp=1; tc++; break; aoqi@0: case "i_udata": vtaExp=2; itaExp=2; vaExp=1; iaExp=1; tc++; break; aoqi@0: case "i_sa1": vtaExp=6; itaExp=6; vaExp=1; iaExp=1; tc++; break; aoqi@0: case "i_tdata": vtaExp=2; itaExp=2; vaExp=1; iaExp=1; tc++; break; aoqi@0: //local class aoqi@0: case "l_odata1": vtaExp=2; itaExp=2; vaExp=1; iaExp=1; tc++; break; aoqi@0: case "l_pdata1": vtaExp=2; itaExp=2; vaExp=1; iaExp=1; tc++; break; aoqi@0: case "l_udata": vtaExp=2; itaExp=2; vaExp=1; iaExp=1; tc++; break; aoqi@0: case "l_sa1": vtaExp=6; itaExp=6; vaExp=1; iaExp=1; tc++; break; aoqi@0: case "l_tdata": vtaExp=2; itaExp=2; vaExp=1; iaExp=1; tc++; break; aoqi@0: //anon class aoqi@0: case "mm_odata1": vtaExp = 2; itaExp=2; vaExp=1; iaExp=1; tc++; break; aoqi@0: case "mm_pdata1": vtaExp = 2; itaExp=2; vaExp=1; iaExp=1; tc++; break; aoqi@0: case "mm_sa1": vtaExp = 6; itaExp=6; vaExp=1; iaExp=1; tc++; break; aoqi@0: case "mm_tdata": vtaExp = 2; itaExp=2; vaExp=1; iaExp=1; tc++; break; aoqi@0: // InnerAnon class aoqi@0: case "ia_odata1": vtaExp=2; itaExp=2; vaExp=1; iaExp=1; tc++; break; aoqi@0: case "ia_pdata1": vtaExp=2; itaExp=2; vaExp=1; iaExp=1; tc++; break; aoqi@0: case "ia_udata": vtaExp=2; itaExp=2; vaExp=1; iaExp=1; tc++; break; aoqi@0: case "ia_sa1": vtaExp=6; itaExp=6; vaExp=1; iaExp=1; tc++; break; aoqi@0: case "ia_tdata": vtaExp=2; itaExp=2; vaExp=1; iaExp=1; tc++; break; aoqi@0: case "IA": vtaExp=4; itaExp=4; vaExp=1; iaExp=1; tc++; break; aoqi@0: case "IN": vtaExp=4; itaExp=4; vaExp=1; iaExp=1; tc++; break; aoqi@0: // default cases are , this$0, this$1, mmtest, atest aoqi@0: default: vtaExp = 0; itaExp=0; vaExp=0; iaExp=0; break; aoqi@0: } aoqi@0: check(testcase,vtaExp, itaExp, vaExp, iaExp, aoqi@0: vtaActual,itaActual,vaActual,iaActual); aoqi@0: } aoqi@0: aoqi@0: public void run() { aoqi@0: ClassFile cf = null; aoqi@0: InputStream in = null; aoqi@0: int testcount = 1; aoqi@0: File testFile = null; aoqi@0: // Generate source, check methods and fields for each combination. aoqi@0: for (Boolean[] bCombo : bRepeat) { aoqi@0: As=bCombo[0]; Bs=bCombo[1]; Cs=bCombo[2]; aoqi@0: Ds=bCombo[3]; TAs=bCombo[4]; TBs=bCombo[5]; aoqi@0: String testname = "Test" + testcount++; aoqi@0: println("Combinations: " + As + ", " + Bs + ", " + Cs + ", " + Ds + aoqi@0: ", " + TAs + ", " + TBs + aoqi@0: "; see " + testname + ".java"); aoqi@0: String[] classes = {testname + ".class", aoqi@0: testname + "$Inner.class", aoqi@0: testname + "$1Local1.class", aoqi@0: testname + "$1.class", aoqi@0: testname + "$1$1.class", aoqi@0: testname + "$1$InnerAnon.class" aoqi@0: }; aoqi@0: // Create test source, create and compile File. aoqi@0: String sourceString = getSource(srcTemplate, testname, aoqi@0: As, Bs, Cs, Ds, TAs, TBs); aoqi@0: System.out.println(sourceString); aoqi@0: try { aoqi@0: testFile = writeTestFile(testname+".java", sourceString); aoqi@0: } aoqi@0: catch (IOException ioe) { ioe.printStackTrace(); } aoqi@0: // Compile test source and read classfile. aoqi@0: File classFile = null; aoqi@0: try { aoqi@0: classFile = compile(testFile); aoqi@0: } aoqi@0: catch (Error err) { aoqi@0: System.err.println("FAILED compile. Source:\n" + sourceString); aoqi@0: throw err; aoqi@0: } aoqi@0: String testloc = classFile.getAbsolutePath().substring( aoqi@0: 0,classFile.getAbsolutePath().indexOf(classFile.getPath())); aoqi@0: for (String clazz : classes) { aoqi@0: try { aoqi@0: cf = ClassFile.read(new File(testloc+clazz)); aoqi@0: } aoqi@0: catch (Exception e) { e.printStackTrace(); } aoqi@0: // Test for all methods and fields aoqi@0: for (Method m: cf.methods) { aoqi@0: test("METHOD", cf, m, null, true); aoqi@0: } aoqi@0: for (Field f: cf.fields) { aoqi@0: test("FIELD", cf, null, f, true); aoqi@0: } aoqi@0: } aoqi@0: } aoqi@0: report(); aoqi@0: if (tc!=xtc) System.out.println("Test Count: " + tc + " != " + aoqi@0: "expected: " + xtc); aoqi@0: } aoqi@0: aoqi@0: aoqi@0: String getSrcTemplate(String sTemplate) { aoqi@0: List tmpl = null; aoqi@0: String sTmpl = ""; aoqi@0: try { aoqi@0: tmpl = Files.readAllLines(new File(testSrc,sTemplate).toPath(), aoqi@0: Charset.defaultCharset()); aoqi@0: } aoqi@0: catch (IOException ioe) { aoqi@0: String error = "FAILED: Test failed to read template" + sTemplate; aoqi@0: ioe.printStackTrace(); aoqi@0: throw new RuntimeException(error); aoqi@0: } aoqi@0: for (String l : tmpl) aoqi@0: sTmpl=sTmpl.concat(l).concat("\n"); aoqi@0: return sTmpl; aoqi@0: } aoqi@0: aoqi@0: // test class template aoqi@0: String getSource(String templateName, String testname, aoqi@0: Boolean Arepeats, Boolean Brepeats, aoqi@0: Boolean Crepeats, Boolean Drepeats, aoqi@0: Boolean TArepeats, Boolean TBrepeats) { aoqi@0: String As = Arepeats ? "@A @A":"@A", aoqi@0: Bs = Brepeats ? "@B @B":"@B", aoqi@0: Cs = Crepeats ? "@C @C":"@C", aoqi@0: Ds = Drepeats ? "@D @D":"@D", aoqi@0: TAs = TArepeats ? "@TA @TA":"@TA", aoqi@0: TBs = TBrepeats ? "@TB @TB":"@TB"; aoqi@0: aoqi@0: // split up replace() lines for readability aoqi@0: String testsource = getSrcTemplate(templateName).replace("testname",testname); aoqi@0: testsource = testsource.replace("_As",As).replace("_Bs",Bs).replace("_Cs",Cs); aoqi@0: testsource = testsource.replace("_Ds",Ds).replace("_TAs",TAs).replace("_TBs",TBs); aoqi@0: return testsource; aoqi@0: } aoqi@0: }