001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * If you wish your version of this file to be governed by only the CDDL
025: * or only the GPL Version 2, indicate your decision by adding
026: * "[Contributor] elects to include this software in this distribution
027: * under the [CDDL or GPL Version 2] license." If you do not indicate a
028: * single choice of license, a recipient has the option to distribute
029: * your version of this file under either the CDDL, the GPL Version 2 or
030: * to extend the choice of license to its licensees as provided above.
031: * However, if you add GPL Version 2 code and therefore, elected the GPL
032: * Version 2 license, then the option applies only if the new code is
033: * made subject to such option by the copyright holder.
034: *
035: * Contributor(s):
036: *
037: * Portions Copyrighted 2008 Sun Microsystems, Inc.
038: */
039:
040: package org.netbeans.junit;
041:
042: import java.io.File;
043: import java.io.FileInputStream;
044: import java.io.FileOutputStream;
045: import java.io.IOException;
046: import java.io.InputStream;
047: import java.lang.reflect.Method;
048: import java.net.URL;
049: import java.net.URLClassLoader;
050: import java.util.ArrayList;
051: import java.util.Collection;
052: import java.util.Enumeration;
053: import java.util.LinkedHashSet;
054: import java.util.List;
055: import java.util.Set;
056: import java.util.TreeSet;
057: import java.util.regex.Matcher;
058: import java.util.regex.Pattern;
059: import junit.framework.Assert;
060: import junit.framework.Test;
061: import junit.framework.TestResult;
062:
063: /**
064: * Wraps a test class with proper NetBeans Runtime Container environment.
065: * This allows to execute tests in a very similar environment to the
066: * actual invocation in the NetBeans IDE. To use write your test as
067: * you are used to and add suite static method:
068: * <pre>
069: * public class YourTest extends NbTestCase {
070: * public YourTest(String s) { super(s); }
071: *
072: * public static Test suite() {
073: * return NbModuleSuite.create(YourTest.class);
074: * }
075: *
076: * public void testXYZ() { ... }
077: * public void testABC() { ... }
078: * }
079: * </pre>
080: *
081: * @since 1.46
082: * @author Jaroslav Tulach <jaroslav.tulach@netbeans.org>
083: */
084: public final class NbModuleSuite extends Object {
085: /** Factory method to create wrapper test that knows how to setup proper
086: * NetBeans Runtime Container environment.
087: * Wraps the provided class into a test that set ups properly the
088: * testing environment. The set of enabled modules is going to be
089: * determined from the actual classpath of a module, which is common
090: * when in all NetBeans tests. All other modules are kept disabled.
091: * In addition,it allows one limit the clusters that shall be made available.
092: * For example <code>ide.*|java.*</code> will start the container just
093: * with platform, ide and java clusters.
094: *
095: *
096: * @param clazz the class with bunch of testXYZ methods
097: * @param clustersRegExp regexp to apply to name of cluster to find out if it is supposed to be included
098: * in the runtime container setup or not
099: * @param moduleRegExp by default all modules on classpath are turned on,
100: * however this regular expression can specify additional ones. If not
101: * null, the specified cluster will be searched for modules with such
102: * codenamebase and those will be turned on
103: * @return runtime container ready test
104: */
105: public static Test create(Class<? extends Test> clazz,
106: String clustersRegExp, String moduleRegExp) {
107: return new S(clazz, clustersRegExp, moduleRegExp);
108: }
109:
110: static final class S extends NbTestSuite {
111: private final Class<?> clazz;
112: private final String clusterRegExp;
113: private final String moduleRegExp;
114:
115: public S(Class<?> aClass, String clusterRegExp,
116: String moduleRegExp) {
117: super ();
118: this .clazz = aClass;
119: this .clusterRegExp = clusterRegExp;
120: this .moduleRegExp = moduleRegExp;
121: }
122:
123: @Override
124: public void run(TestResult result) {
125: try {
126: runInRuntimeContainer(result);
127: } catch (Exception ex) {
128: result.addError(this , ex);
129: }
130: }
131:
132: private void runInRuntimeContainer(TestResult result)
133: throws Exception {
134: File platform = findPlatform();
135: File[] boot = new File(platform, "lib").listFiles();
136: List<URL> bootCP = new ArrayList<URL>();
137: for (int i = 0; i < boot.length; i++) {
138: URL u = boot[i].toURL();
139: if (u.toExternalForm().endsWith(".jar")) {
140: bootCP.add(u);
141: }
142: }
143:
144: File tools = new File(new File(new File(System
145: .getProperty("java.home")).getParentFile(), "lib"),
146: "tools.jar");
147: Assert.assertTrue(tools.exists());
148: bootCP.add(tools.toURL());
149:
150: // loader that does not see our current classloader
151: ClassLoader parent = ClassLoader.getSystemClassLoader()
152: .getParent();
153: URLClassLoader loader = new URLClassLoader(bootCP
154: .toArray(new URL[0]), parent);
155: Class<?> main = loader.loadClass("org.netbeans.Main"); // NOI18N
156: Assert.assertEquals("Loaded by our classloader", loader,
157: main.getClassLoader());
158: Method m = main.getDeclaredMethod("main", String[].class); // NOI18N
159:
160: System.setProperty("java.util.logging.config", "-");
161: System.setProperty("netbeans.logger.console", "true");
162: System.setProperty("netbeans.home", platform.getPath());
163:
164: File ud = new File(new File(Manager.getWorkDirPath()),
165: "userdir");
166: ud.mkdirs();
167: NbTestCase.deleteSubFiles(ud);
168:
169: System.setProperty("netbeans.user", ud.getPath());
170:
171: TreeSet<String> modules = new TreeSet<String>();
172: modules.addAll(findEnabledModules(NbTestSuite.class
173: .getClassLoader()));
174: modules.add("org.openide.filesystems");
175: modules.add("org.openide.modules");
176: modules.add("org.openide.util");
177: modules.remove("org.netbeans.insane");
178: modules.add("org.netbeans.core.startup");
179: modules.add("org.netbeans.bootstrap");
180: turnModules(ud, modules, moduleRegExp, platform);
181:
182: StringBuilder sb = new StringBuilder();
183: String sep = "";
184: for (File f : findClusters()) {
185: turnModules(ud, modules, moduleRegExp, f);
186: sb.append(sep);
187: sb.append(f.getPath());
188: sep = File.pathSeparator;
189: }
190: System.setProperty("netbeans.dirs", sb.toString());
191:
192: System.setProperty("netbeans.security.nocheck", "true");
193:
194: List<String> args = new ArrayList<String>();
195: args.add("--nosplash");
196: m.invoke(null, (Object) args.toArray(new String[0]));
197:
198: ClassLoader global = Thread.currentThread()
199: .getContextClassLoader();
200: Assert.assertNotNull("Global classloader is initialized",
201: global);
202:
203: URL[] testCP = preparePath(clazz);
204: JUnitLoader testLoader = new JUnitLoader(testCP, global,
205: NbTestSuite.class.getClassLoader());
206: Class<?> sndClazz = testLoader.loadClass(clazz.getName());
207:
208: new NbTestSuite(sndClazz).run(result);
209: }
210:
211: private URL[] preparePath(Class<?>... classes) {
212: Collection<URL> cp = new LinkedHashSet<URL>();
213: for (Class c : classes) {
214: URL test = c.getProtectionDomain().getCodeSource()
215: .getLocation();
216: Assert.assertNotNull("URL found for " + c, test);
217: cp.add(test);
218: }
219: return cp.toArray(new URL[0]);
220: }
221:
222: private File findPlatform() {
223: try {
224: Class<?> lookup = Class
225: .forName("org.openide.util.Lookup"); // NOI18N
226: File util = new File(lookup.getProtectionDomain()
227: .getCodeSource().getLocation().toURI());
228: Assert
229: .assertTrue("Util exists: " + util, util
230: .exists());
231:
232: return util.getParentFile().getParentFile();
233: } catch (Exception ex) {
234: Assert.fail("Cannot find utilities JAR");
235: return null;
236: }
237: }
238:
239: private File[] findClusters() {
240: if (clusterRegExp == null) {
241: return new File[0];
242: }
243:
244: List<File> clusters = new ArrayList<File>();
245: File plat = findPlatform();
246:
247: for (File f : plat.getParentFile().listFiles()) {
248: if (f.equals(plat)) {
249: continue;
250: }
251: if (!f.getName().matches(clusterRegExp)) {
252: continue;
253: }
254: File m = new File(new File(f, "config"), "Modules");
255: if (m.exists()) {
256: clusters.add(f);
257: }
258: }
259: return clusters.toArray(new File[0]);
260: }
261:
262: private static Pattern CODENAME = Pattern.compile(
263: "OpenIDE-Module: *([^/$ \n\r]*)[/]?[0-9]*",
264: Pattern.MULTILINE);
265:
266: /** Looks for all modules on classpath of given loader and builds
267: * their list from them.
268: */
269: static Set<String> findEnabledModules(ClassLoader loader)
270: throws IOException {
271: Set<String> cnbs = new TreeSet<String>();
272:
273: Enumeration<URL> en = loader
274: .getResources("META-INF/MANIFEST.MF");
275: while (en.hasMoreElements()) {
276: URL url = en.nextElement();
277: String manifest = asString(url.openStream(), true);
278: Matcher m = CODENAME.matcher(manifest);
279: if (m.find()) {
280: cnbs.add(m.group(1));
281: }
282: }
283:
284: return cnbs;
285: }
286:
287: private static String asString(InputStream is, boolean close)
288: throws IOException {
289: byte[] arr = new byte[is.available()];
290: int len = is.read(arr);
291: if (len != arr.length) {
292: throw new IOException("Not fully read: " + arr.length
293: + " was " + len);
294: }
295: if (close) {
296: is.close();
297: }
298: return new String(arr, "UTF-8"); // NOI18N
299: }
300:
301: private static final class JUnitLoader extends URLClassLoader {
302: private final ClassLoader junit;
303:
304: public JUnitLoader(URL[] urls, ClassLoader parent,
305: ClassLoader junit) {
306: super (urls, parent);
307: this .junit = junit;
308: }
309:
310: @Override
311: protected Class<?> findClass(String name)
312: throws ClassNotFoundException {
313: if (isUnit(name)) {
314: return junit.loadClass(name);
315: }
316: return super .findClass(name);
317: }
318:
319: @Override
320: public URL findResource(String name) {
321: if (isUnit(name)) {
322: return junit.getResource(name);
323: }
324: return super .findResource(name);
325: }
326:
327: @Override
328: public Enumeration<URL> findResources(String name)
329: throws IOException {
330: if (isUnit(name)) {
331: return junit.getResources(name);
332: }
333: return super .findResources(name);
334: }
335:
336: private final boolean isUnit(String res) {
337: if (res.startsWith("junit")) {
338: return true;
339: }
340: if (res.startsWith("org.junit")
341: || res.startsWith("org/junit")) {
342: return true;
343: }
344: if (res.startsWith("org.netbeans.junit")
345: || res.startsWith("org/netbeans/junit")) {
346: return true;
347: }
348: return false;
349: }
350: }
351:
352: private static Pattern ENABLED = Pattern.compile(
353: "<param name=[\"']enabled[\"']>([^<]*)</param>",
354: Pattern.MULTILINE);
355:
356: private static void turnModules(File ud,
357: TreeSet<String> modules, String regExp,
358: File... clusterDirs) throws IOException {
359: File config = new File(new File(ud, "config"), "Modules");
360: config.mkdirs();
361:
362: Pattern modPattern = regExp == null ? null : Pattern
363: .compile(regExp);
364: for (File c : clusterDirs) {
365: File modulesDir = new File(new File(c, "config"),
366: "Modules");
367: for (File m : modulesDir.listFiles()) {
368: String n = m.getName();
369: if (n.endsWith(".xml")) {
370: n = n.substring(0, n.length() - 4);
371: }
372: n = n.replace('-', '.');
373:
374: String xml = asString(new FileInputStream(m), true);
375: Matcher matcherEnabled = ENABLED.matcher(xml);
376: // Matcher matcherEager = EAGER.matcher(xml);
377:
378: boolean found = matcherEnabled.find();
379: boolean contains = modules.contains(n);
380: if (!contains && modPattern != null) {
381: contains = modPattern.matcher(n).matches();
382: }
383: boolean enabled = found
384: && "true".equals(matcherEnabled.group(1));
385: if (contains == enabled) {
386: continue;
387: }
388:
389: if (found) {
390: assert matcherEnabled.groupCount() == 1 : "Groups: "
391: + matcherEnabled.groupCount()
392: + " for:\n" + xml;
393:
394: try {
395: String out = xml.substring(0,
396: matcherEnabled.start(1))
397: + (contains ? "true" : "false")
398: + xml.substring(matcherEnabled
399: .end(1));
400: writeModule(new File(config, m.getName()),
401: out);
402: } catch (IllegalStateException ex) {
403: throw (IOException) new IOException(
404: "Unparsable:\n" + xml)
405: .initCause(ex);
406: }
407: }
408: }
409: }
410: }
411:
412: private static void writeModule(File file, String xml)
413: throws IOException {
414: FileOutputStream os = new FileOutputStream(file);
415: os.write(xml.getBytes("UTF-8"));
416: os.close();
417: }
418: } // end of S
419: }
|