From f2edf2d0a50911244d776cf48bce95439333cc3c Mon Sep 17 00:00:00 2001 From: antelder Date: Wed, 28 Oct 2009 08:55:19 +0000 Subject: [maven-release-plugin] copy for tag maven-osgi-junit-plugin-1.0 git-svn-id: http://svn.us.apache.org/repos/asf/tuscany@830470 13f79535-47bb-0310-9956-ffa450edef68 --- .../tags/maven-osgi-junit-plugin-1.0/LICENSE | 205 +++ .../tags/maven-osgi-junit-plugin-1.0/NOTICE | 6 + .../tags/maven-osgi-junit-plugin-1.0/README | 67 + .../tags/maven-osgi-junit-plugin-1.0/RELEASE_NOTES | 4 + .../tags/maven-osgi-junit-plugin-1.0/pom.xml | 302 +++++ .../maven/plugin/surefire/ForkConfiguration.java | 239 ++++ .../maven/plugin/surefire/OSGiSurefireBooter.java | 1018 +++++++++++++++ .../maven/plugin/surefire/OSGiSurefirePlugin.java | 1310 ++++++++++++++++++++ 8 files changed, 3151 insertions(+) create mode 100644 maven-plugins/tags/maven-osgi-junit-plugin-1.0/LICENSE create mode 100644 maven-plugins/tags/maven-osgi-junit-plugin-1.0/NOTICE create mode 100644 maven-plugins/tags/maven-osgi-junit-plugin-1.0/README create mode 100644 maven-plugins/tags/maven-osgi-junit-plugin-1.0/RELEASE_NOTES create mode 100644 maven-plugins/tags/maven-osgi-junit-plugin-1.0/pom.xml create mode 100644 maven-plugins/tags/maven-osgi-junit-plugin-1.0/src/main/java/org/apache/tuscany/maven/plugin/surefire/ForkConfiguration.java create mode 100644 maven-plugins/tags/maven-osgi-junit-plugin-1.0/src/main/java/org/apache/tuscany/maven/plugin/surefire/OSGiSurefireBooter.java create mode 100644 maven-plugins/tags/maven-osgi-junit-plugin-1.0/src/main/java/org/apache/tuscany/maven/plugin/surefire/OSGiSurefirePlugin.java (limited to 'maven-plugins/tags') diff --git a/maven-plugins/tags/maven-osgi-junit-plugin-1.0/LICENSE b/maven-plugins/tags/maven-osgi-junit-plugin-1.0/LICENSE new file mode 100644 index 0000000000..6e529a25c4 --- /dev/null +++ b/maven-plugins/tags/maven-osgi-junit-plugin-1.0/LICENSE @@ -0,0 +1,205 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + diff --git a/maven-plugins/tags/maven-osgi-junit-plugin-1.0/NOTICE b/maven-plugins/tags/maven-osgi-junit-plugin-1.0/NOTICE new file mode 100644 index 0000000000..12c7095949 --- /dev/null +++ b/maven-plugins/tags/maven-osgi-junit-plugin-1.0/NOTICE @@ -0,0 +1,6 @@ +Apache Tuscany Maven OSGi JUnit Plugin +Copyright (c) 2005 - 2009 The Apache Software Foundation + +This product includes software developed by +The Apache Software Foundation (http://www.apache.org/). + diff --git a/maven-plugins/tags/maven-osgi-junit-plugin-1.0/README b/maven-plugins/tags/maven-osgi-junit-plugin-1.0/README new file mode 100644 index 0000000000..9466d5f8cd --- /dev/null +++ b/maven-plugins/tags/maven-osgi-junit-plugin-1.0/README @@ -0,0 +1,67 @@ +A Maven plugin used by the Tuscany SCA project for OSGi JUnit testing. +Not exactly clear what its does, there's some discusssion about it at: +http://apache.markmail.org/message/uxt7afrquwc75qxj + +To build, from the top maven-bundle-plugin run maven: + +mvn + +or once all the dependencies have been downloaded and a succesful build run use: + +mvn clean install -o + +So as to avoid the Tuscany SCA project using SNAPSHOT dependencies any changes +to this module should be released and the Tuscany SCA project updated to use +the newly released version. + +To release this module: + +mvn release:prepare + +followed by: + +mvn release:perform + +That will automatically create an SVN tag from the release, update the version +numbers in the pom.xml files in the trunk and tag, and deploy the artifacts to the +staging repository defined by the in your Maven settings.xml. +While running it will prompt you for the names for the tag, release version etc. + +In your maven settings.xml file you must have a server defined named "apache.releases", +and a profile named "release". For example: + + + ... + + apache.releases + antelder + \ant\id_dsa + xxx + 775 + 664 + + + + + ... + + release + + ... + apache.releases::default::scp://people.apache.org/home/antelder/public_html/tuscany/maven-osgi-junit-plugin-1.0 + + + + +Call a vote to release the module, eg: http://apache.markmail.org/message/6jnlfxbx7uklt5nv + +After a successful vote copy the staging artifacts to the live repository, eg: + +cp -p -v -R maven-bundle-plugin-1.0/org/apache/tuscany/maven/plugins/ /x1/www/people.apache.org/repo/m2-ibiblio-rsync-repository/org/apache/tuscany/maven/plugins + + + + + + + diff --git a/maven-plugins/tags/maven-osgi-junit-plugin-1.0/RELEASE_NOTES b/maven-plugins/tags/maven-osgi-junit-plugin-1.0/RELEASE_NOTES new file mode 100644 index 0000000000..deb1a5b46c --- /dev/null +++ b/maven-plugins/tags/maven-osgi-junit-plugin-1.0/RELEASE_NOTES @@ -0,0 +1,4 @@ +Apache Tuscany Maven OSGi JUNit Plugin 1.0 Release Notes +======================================================== + +Initial release diff --git a/maven-plugins/tags/maven-osgi-junit-plugin-1.0/pom.xml b/maven-plugins/tags/maven-osgi-junit-plugin-1.0/pom.xml new file mode 100644 index 0000000000..7354c05d48 --- /dev/null +++ b/maven-plugins/tags/maven-osgi-junit-plugin-1.0/pom.xml @@ -0,0 +1,302 @@ + + + + 4.0.0 + + org.apache + apache + 4 + + + org.apache.tuscany.maven.plugins + maven-osgi-junit-plugin + maven-plugin + Apache Tuscany OSGi JUnit Maven Plugin + 1.0 + + + scm:svn:http://svn.apache.org/repos/asf/tuscany/maven-plugins/tags/maven-osgi-junit-plugin-1.0 + scm:svn:https://svn.apache.org/repos/asf/tuscany/maven-plugins/tags/maven-osgi-junit-plugin-1.0 + scm:svn:https://svn.apache.org/repos/asf/tuscany/maven-plugins/tags/maven-osgi-junit-plugin-1.0 + + + + + apache.releases + Apache Release Distribution Repository + scp://people.apache.org/www/people.apache.org/repo/m2-ibiblio-rsync-repository + + + apache.snapshots + Apache Development Snapshot Repository + scp://people.apache.org/www/people.apache.org/repo/m2-snapshot-repository + false + + + + + + + apache.snapshots + Apache SNAPSHOT Repository + http://people.apache.org/repo/m2-snapshot-repository + + false + + + true + + + + + tuscany.repo + Tuscany Maven 2.x Repository + http://svn.apache.org/repos/asf/tuscany/maven + + true + + + false + + + + + + + + apache + Apache Repository + http://people.apache.org/repo/m2-ibiblio-rsync-repository + + true + + + false + + + + + + apache.snapshots + Apache SNAPSHOT Repository + http://people.apache.org/repo/m2-snapshot-repository + + false + + + true + + + + + + + + release + + + + + true + maven-deploy-plugin + 2.4 + + ${deploy.altRepository} + true + + + + + maven-gpg-plugin + 1.0-alpha-4 + + + + sign + + + + + + + + + + + deploy + + deploy + + + org.apache.maven.plugins + maven-source-plugin + 2.0.4 + + + attach-sources + + jar + + + + + + + + + + + + + org.apache.maven + maven-plugin-api + 2.0.8 + + + + org.apache.maven + maven-project + 2.0.8 + + + + org.apache.maven.surefire + surefire-booter + 2.4.3 + + + + org.apache.maven + maven-core + 2.0.8 + + + org.apache.maven + maven-toolchain + 1.0 + + + + org.apache.tuscany.sca + tuscany-node-launcher-equinox + 2.0-M3 + runtime + true + + + + org.eclipse + osgi + 3.5.0-v20090520 + compile + + + + + + + install + + + + src/main/resources + + + . + META-INF + true + + LICENSE + NOTICE + + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.0.2 + + 1.5 + 1.5 + + + + + + org.apache.maven.plugins + maven-jar-plugin + 2.1 + + + + ${project.artifactId} + ${name} + The Apache Software Foundation + ${version} + ${name} + org.apache + The Apache Software Foundation + ${version} + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.4.3 + + + **/*TestCase.java + + brief + false + once + -ea -Xmx256m + + + + + org.apache.maven.plugins + maven-release-plugin + + https://svn.apache.org/repos/asf/tuscany/maven-plugins/tags + false + clean install + deploy + -Prelease,deploy + true + + + + + + + + + + diff --git a/maven-plugins/tags/maven-osgi-junit-plugin-1.0/src/main/java/org/apache/tuscany/maven/plugin/surefire/ForkConfiguration.java b/maven-plugins/tags/maven-osgi-junit-plugin-1.0/src/main/java/org/apache/tuscany/maven/plugin/surefire/ForkConfiguration.java new file mode 100644 index 0000000000..eb391970ca --- /dev/null +++ b/maven-plugins/tags/maven-osgi-junit-plugin-1.0/src/main/java/org/apache/tuscany/maven/plugin/surefire/ForkConfiguration.java @@ -0,0 +1,239 @@ +package org.apache.tuscany.maven.plugin.surefire; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; + +import org.apache.maven.surefire.booter.SurefireBooterForkException; +import org.apache.maven.surefire.booter.shade.org.codehaus.plexus.util.cli.Commandline; +import org.apache.maven.surefire.util.UrlUtils; +import org.codehaus.plexus.util.StringUtils; + +/** + * Derived from surefire-booter 2.4.3 + * Configuration for forking tests. + * + */ +@SuppressWarnings("unchecked") +public class ForkConfiguration { + public static final String FORK_ONCE = "once"; + + public static final String FORK_ALWAYS = "always"; + + public static final String FORK_NEVER = "never"; + + private String forkMode; + + private boolean useSystemClassLoader; + private boolean useManifestOnlyJar; + + private Properties systemProperties; + + private String jvmExecutable; + + private String argLine; + + private Map environmentVariables; + + private File workingDirectory; + + private boolean debug; + + private String debugLine; + + public void setForkMode(String forkMode) { + if ("pertest".equalsIgnoreCase(forkMode)) { + this.forkMode = FORK_ALWAYS; + } else if ("none".equalsIgnoreCase(forkMode)) { + this.forkMode = FORK_NEVER; + } else if (forkMode.equals(FORK_NEVER) || forkMode.equals(FORK_ONCE) || forkMode.equals(FORK_ALWAYS)) { + this.forkMode = forkMode; + } else { + throw new IllegalArgumentException("Fork mode " + forkMode + " is not a legal value"); + } + } + + public boolean isForking() { + return !FORK_NEVER.equals(forkMode); + } + + public void setUseSystemClassLoader(boolean useSystemClassLoader) { + this.useSystemClassLoader = useSystemClassLoader; + } + + public boolean isUseSystemClassLoader() { + return useSystemClassLoader; + } + + public void setSystemProperties(Properties systemProperties) { + this.systemProperties = (Properties)systemProperties.clone(); + } + + public void setJvmExecutable(String jvmExecutable) { + this.jvmExecutable = jvmExecutable; + } + + public void setArgLine(String argLine) { + this.argLine = argLine; + } + + public void setDebugLine(String debugLine) { + this.debugLine = debugLine; + } + + public void setEnvironmentVariables(Map environmentVariables) { + this.environmentVariables = new HashMap(environmentVariables); + } + + public void setWorkingDirectory(File workingDirectory) { + this.workingDirectory = workingDirectory; + } + + public String getForkMode() { + return forkMode; + } + + public Properties getSystemProperties() { + return systemProperties; + } + + /** + * @throws SurefireBooterForkException + * @deprecated use the 2-arg alternative. + */ + public Commandline createCommandLine(List classPath) throws SurefireBooterForkException { + return createCommandLine(classPath, false); + } + + public Commandline createCommandLine(List classPath, boolean useJar) throws SurefireBooterForkException { + Commandline cli = new Commandline(); + + cli.setExecutable(jvmExecutable); + + if (argLine != null) { + cli.createArg().setLine(argLine); + } + + if (environmentVariables != null) { + Iterator iter = environmentVariables.keySet().iterator(); + + while (iter.hasNext()) { + String key = (String)iter.next(); + + String value = (String)environmentVariables.get(key); + + cli.addEnvironment(key, value); + } + } + + if (debugLine != null && !"".equals(debugLine)) { + cli.createArg().setLine(debugLine); + } + + if (useJar) { + File jarFile; + try { + jarFile = createJar(classPath); + } catch (IOException e) { + throw new SurefireBooterForkException("Error creating archive file", e); + } + + cli.createArg().setValue("-jar"); + + cli.createArg().setValue(jarFile.getAbsolutePath()); + } else { + cli.createArg().setValue("-classpath"); + + cli.createArg().setValue(StringUtils.join(classPath.iterator(), File.pathSeparator)); + + cli.createArg().setValue(OSGiSurefireBooter.class.getName()); + } + + cli.setWorkingDirectory(workingDirectory.getAbsolutePath()); + + return cli; + } + + /** + * Create a jar with just a manifest containing a Main-Class entry for SurefireBooter and a Class-Path entry + * for all classpath elements. + * + * @param classPath List<String> of all classpath elements. + * @return + * @throws IOException + */ + private File createJar(List classPath) throws IOException { + File file = File.createTempFile("surefirebooter", ".jar"); + if (!debug) { + file.deleteOnExit(); + } + FileOutputStream fos = new FileOutputStream(file); + JarOutputStream jos = new JarOutputStream(fos); + jos.setLevel(JarOutputStream.STORED); + JarEntry je = new JarEntry("META-INF/MANIFEST.MF"); + jos.putNextEntry(je); + + Manifest man = new Manifest(); + + // we can't use StringUtils.join here since we need to add a '/' to + // the end of directory entries - otherwise the jvm will ignore them. + String cp = ""; + for (Iterator it = classPath.iterator(); it.hasNext();) { + String el = (String)it.next(); + // NOTE: if File points to a directory, this entry MUST end in '/'. + cp += UrlUtils.getURL(new File(el)).toExternalForm() + " "; + } + + man.getMainAttributes().putValue("Manifest-Version", "1.0"); + man.getMainAttributes().putValue("Class-Path", cp.trim()); + man.getMainAttributes().putValue("Main-Class", OSGiSurefireBooter.class.getName()); + + man.write(jos); + jos.close(); + + return file; + } + + public void setDebug(boolean debug) { + this.debug = debug; + } + + public boolean isDebug() { + return debug; + } + + public void setUseManifestOnlyJar(boolean useManifestOnlyJar) { + this.useManifestOnlyJar = useManifestOnlyJar; + } + + public boolean isUseManifestOnlyJar() { + return useManifestOnlyJar; + } +} diff --git a/maven-plugins/tags/maven-osgi-junit-plugin-1.0/src/main/java/org/apache/tuscany/maven/plugin/surefire/OSGiSurefireBooter.java b/maven-plugins/tags/maven-osgi-junit-plugin-1.0/src/main/java/org/apache/tuscany/maven/plugin/surefire/OSGiSurefireBooter.java new file mode 100644 index 0000000000..e59c432f69 --- /dev/null +++ b/maven-plugins/tags/maven-osgi-junit-plugin-1.0/src/main/java/org/apache/tuscany/maven/plugin/surefire/OSGiSurefireBooter.java @@ -0,0 +1,1018 @@ +package org.apache.tuscany.maven.plugin.surefire; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; + +import org.apache.maven.surefire.Surefire; +import org.apache.maven.surefire.booter.SurefireBooterForkException; +import org.apache.maven.surefire.booter.SurefireExecutionException; +import org.apache.maven.surefire.booter.output.FileOutputConsumerProxy; +import org.apache.maven.surefire.booter.output.ForkingStreamConsumer; +import org.apache.maven.surefire.booter.output.OutputConsumer; +import org.apache.maven.surefire.booter.output.StandardOutputConsumer; +import org.apache.maven.surefire.booter.output.SupressFooterOutputConsumerProxy; +import org.apache.maven.surefire.booter.output.SupressHeaderOutputConsumerProxy; +import org.apache.maven.surefire.booter.shade.org.codehaus.plexus.util.IOUtil; +import org.apache.maven.surefire.booter.shade.org.codehaus.plexus.util.StringUtils; +import org.apache.maven.surefire.booter.shade.org.codehaus.plexus.util.cli.Commandline; +import org.apache.maven.surefire.booter.shade.org.codehaus.plexus.util.cli.StreamConsumer; +import org.apache.maven.surefire.testset.TestSetFailedException; +import org.apache.maven.surefire.util.NestedRuntimeException; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; + +/** + * Derived from surefire-booter 2.4.3 + */ +public class OSGiSurefireBooter { + private static final String TEST_SUITE_PROPERTY_PREFIX = "testSuite."; + private static final String REPORT_PROPERTY_PREFIX = "report."; + private static final String PARAMS_SUFIX = ".params"; + private static final String TYPES_SUFIX = ".types"; + + /** + * A classloader that delegates an OSGi bundle + */ + static class BundleClassLoader extends ClassLoader { + private Bundle bundle; + + public BundleClassLoader(Bundle bundle, ClassLoader parent) { + super(parent); + this.bundle = bundle; + } + + @Override + protected Class findClass(String name) throws ClassNotFoundException { + return bundle.loadClass(name); + } + + @Override + protected URL findResource(String name) { + return bundle.getResource(name); + } + + @Override + protected Enumeration findResources(String name) throws IOException { + return bundle.getResources(name); + } + + } + + private List reports = new ArrayList(); + + /** + * Test classpath + */ + private List classPathUrls = new ArrayList(); + + /** + * surefire provider classpath + */ + private List surefireClassPathUrls = new ArrayList(); + + /** + * surefire boot classpath + */ + private List surefireBootClassPathUrls = new ArrayList(); + + private List testSuites = new ArrayList(); + + private boolean failIfNoTests = false; + + private int forkedProcessTimeoutInSeconds = 0; + + private boolean redirectTestOutputToFile = false; + + // ---------------------------------------------------------------------- + // + // ---------------------------------------------------------------------- + + private ForkConfiguration forkConfiguration; + + public static final int TESTS_SUCCEEDED_EXIT_CODE = 0; + + public static final int TESTS_FAILED_EXIT_CODE = 255; + + public static final int NO_TESTS_EXIT_CODE = 254; + + private static Method assertionStatusMethod; + + /** + * @deprecated because the OSGi classloader is really isolated - no parent. + */ + private boolean childDelegation = true; + + private File reportsDirectory; + + /** + * This field is set to true if it's running from main. It's used to help decide what classloader to use. + */ + private final boolean isForked; + + /** + * Whether to enable assertions or not (can be affected by the fork arguments, and the ability to do so based on the + * JVM). + */ + private boolean enableAssertions; + + static { + try { + assertionStatusMethod = + ClassLoader.class.getMethod("setDefaultAssertionStatus", new Class[] {boolean.class}); + } catch (NoSuchMethodException e) { + assertionStatusMethod = null; + } + } + + public OSGiSurefireBooter() { + isForked = false; + } + + private OSGiSurefireBooter(boolean isForked) { + this.isForked = isForked; + } + + // ---------------------------------------------------------------------- + // Accessors + // ---------------------------------------------------------------------- + + public void addReport(String report) { + addReport(report, null); + } + + public void addReport(String report, Object[] constructorParams) { + reports.add(new Object[] {report, constructorParams}); + } + + public void addTestSuite(String suiteClassName, Object[] constructorParams) { + testSuites.add(new Object[] {suiteClassName, constructorParams}); + } + + public void addClassPathUrl(String path) { + if (!classPathUrls.contains(path)) { + classPathUrls.add(path); + } + } + + public void addSurefireClassPathUrl(String path) { + if (!surefireClassPathUrls.contains(path)) { + surefireClassPathUrls.add(path); + } + } + + public void addSurefireBootClassPathUrl(String path) { + if (!surefireBootClassPathUrls.contains(path)) { + surefireBootClassPathUrls.add(path); + } + } + + /** + * Setting this to true will cause a failure if there are no tests to run + * + * @param redirectTestOutputToFile + */ + public void setFailIfNoTests(boolean failIfNoTests) { + this.failIfNoTests = failIfNoTests; + } + + /** + * When forking, setting this to true will make the test output to be saved in a file instead of showing it on the + * standard output + * + * @param redirectTestOutputToFile + */ + public void setRedirectTestOutputToFile(boolean redirectTestOutputToFile) { + this.redirectTestOutputToFile = redirectTestOutputToFile; + } + + /** + * Set the directory where reports will be saved + * + * @param reportsDirectory the directory + */ + public void setReportsDirectory(File reportsDirectory) { + this.reportsDirectory = reportsDirectory; + } + + /** + * Get the directory where reports will be saved + */ + public File getReportsDirectory() { + return reportsDirectory; + } + + public void setForkConfiguration(ForkConfiguration forkConfiguration) { + this.forkConfiguration = forkConfiguration; + } + + public boolean isForking() { + return forkConfiguration.isForking(); + } + + public int run() throws SurefireBooterForkException, SurefireExecutionException { + int result; + + if (ForkConfiguration.FORK_NEVER.equals(forkConfiguration.getForkMode())) { + result = runSuitesInProcess(); + } else if (ForkConfiguration.FORK_ONCE.equals(forkConfiguration.getForkMode())) { + result = runSuitesForkOnce(); + } else if (ForkConfiguration.FORK_ALWAYS.equals(forkConfiguration.getForkMode())) { + result = runSuitesForkPerTestSet(); + } else { + throw new SurefireExecutionException("Unknown forkmode: " + forkConfiguration.getForkMode(), null); + } + return result; + } + + private int runSuitesInProcess(String testSet, Properties results) throws SurefireExecutionException { + if (testSuites.size() != 1) { + throw new IllegalArgumentException("Cannot only specify testSet for single test suites"); + } + + // TODO: replace with plexus + + // noinspection CatchGenericClass,OverlyBroadCatchBlock + ClassLoader oldContextClassLoader = Thread.currentThread().getContextClassLoader(); + try { + start(); + } catch (Exception e1) { + throw new SurefireExecutionException(e1.getMessage(), e1); + } + try { + ClassLoader testsClassLoader = + useSystemClassLoader() ? ClassLoader.getSystemClassLoader() : createClassLoader(classPathUrls, + null, + childDelegation); + + // TODO: assertions = true shouldn't be required for this CL if we had proper separation (see TestNG) + ClassLoader surefireClassLoader = createClassLoader(surefireClassPathUrls, testsClassLoader); + + Class surefireClass = surefireClassLoader.loadClass(Surefire.class.getName()); + + Object surefire = surefireClass.newInstance(); + + Method run = + surefireClass.getMethod("run", new Class[] {List.class, Object[].class, String.class, + ClassLoader.class, ClassLoader.class, Properties.class, + Boolean.class}); + + Thread.currentThread().setContextClassLoader(testsClassLoader); + + Integer result = + (Integer)run.invoke(surefire, new Object[] {reports, testSuites.get(0), testSet, surefireClassLoader, + testsClassLoader, results, new Boolean(failIfNoTests)}); + + return result.intValue(); + } catch (InvocationTargetException e) { + throw new SurefireExecutionException(e.getTargetException().getMessage(), e.getTargetException()); + } catch (Exception e) { + throw new SurefireExecutionException("Unable to instantiate and execute Surefire", e); + } finally { + stop(); + Thread.currentThread().setContextClassLoader(oldContextClassLoader); + } + } + + private int runSuitesInProcess() throws SurefireExecutionException { + // TODO: replace with plexus + + // noinspection CatchGenericClass,OverlyBroadCatchBlock + ClassLoader oldContextClassLoader = Thread.currentThread().getContextClassLoader(); + try { + start(); + } catch (Exception e1) { + throw new SurefireExecutionException(e1.getMessage(), e1); + } + + try { + // The test classloader must be constructed first to avoid issues with commons-logging until we properly + // separate the TestNG classloader + ClassLoader testsClassLoader; + String testClassPath = getTestClassPathAsString(); + System.setProperty("surefire.test.class.path", testClassPath); + if (useManifestOnlyJar()) { + // testsClassLoader = getClass().getClassLoader(); // ClassLoader.getSystemClassLoader() + testsClassLoader = createClassLoader(classPathUrls, null, childDelegation); + // SUREFIRE-459, trick the app under test into thinking its classpath was conventional (instead of a single manifest-only jar) + System.setProperty("surefire.real.class.path", System.getProperty("java.class.path")); + System.setProperty("java.class.path", testClassPath); + } else { + testsClassLoader = createClassLoader(classPathUrls, null, childDelegation); + } + + ClassLoader surefireClassLoader = createClassLoader(surefireClassPathUrls, testsClassLoader); + + Class surefireClass = surefireClassLoader.loadClass(Surefire.class.getName()); + + Object surefire = surefireClass.newInstance(); + + Method run = + surefireClass.getMethod("run", new Class[] {List.class, List.class, ClassLoader.class, + ClassLoader.class, Boolean.class}); + + Thread.currentThread().setContextClassLoader(testsClassLoader); + + Integer result = + (Integer)run.invoke(surefire, new Object[] {reports, testSuites, surefireClassLoader, testsClassLoader, + new Boolean(failIfNoTests)}); + + return result.intValue(); + } catch (InvocationTargetException e) { + throw new SurefireExecutionException(e.getTargetException().getMessage(), e.getTargetException()); + } catch (Exception e) { + throw new SurefireExecutionException("Unable to instantiate and execute Surefire", e); + } finally { + stop(); + Thread.currentThread().setContextClassLoader(oldContextClassLoader); + } + } + + private String getTestClassPathAsString() { + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < classPathUrls.size(); i++) { + sb.append(classPathUrls.get(i)).append(File.pathSeparatorChar); + } + return sb.toString(); + } + + private int runSuitesForkOnce() throws SurefireBooterForkException { + return forkSuites(testSuites, true, true); + } + + private int runSuitesForkPerTestSet() throws SurefireBooterForkException { + ClassLoader testsClassLoader; + ClassLoader surefireClassLoader; + try { + testsClassLoader = createClassLoader(classPathUrls, null, false); + // TODO: assertions = true shouldn't be required if we had proper separation (see TestNG) + surefireClassLoader = createClassLoader(surefireClassPathUrls, testsClassLoader, false); + } catch (MalformedURLException e) { + throw new SurefireBooterForkException("Unable to create classloader to find test suites", e); + } + + int globalResult = 0; + + boolean showHeading = true; + Properties properties = new Properties(); + for (Iterator i = testSuites.iterator(); i.hasNext();) { + Object[] testSuite = (Object[])i.next(); + + Map testSets = getTestSets(testSuite, testsClassLoader, surefireClassLoader); + + for (Iterator j = testSets.keySet().iterator(); j.hasNext();) { + Object testSet = j.next(); + boolean showFooter = !j.hasNext() && !i.hasNext(); + int result = forkSuite(testSuite, testSet, showHeading, showFooter, properties); + if (result > globalResult) { + globalResult = result; + } + showHeading = false; + } + } + + return globalResult; + } + + private Map getTestSets(Object[] testSuite, ClassLoader testsClassLoader, ClassLoader surefireClassLoader) + throws SurefireBooterForkException { + String className = (String)testSuite[0]; + + Object[] params = (Object[])testSuite[1]; + + Object suite; + try { + suite = Surefire.instantiateObject(className, params, surefireClassLoader); + } catch (TestSetFailedException e) { + throw new SurefireBooterForkException(e.getMessage(), e.getCause()); + } catch (ClassNotFoundException e) { + throw new SurefireBooterForkException("Unable to find class for test suite '" + className + "'", e); + } catch (NoSuchMethodException e) { + throw new SurefireBooterForkException("Unable to find appropriate constructor for test suite '" + className + + "': " + + e.getMessage(), e); + } + + Map testSets; + try { + Method m = suite.getClass().getMethod("locateTestSets", new Class[] {ClassLoader.class}); + + testSets = (Map)m.invoke(suite, new Object[] {testsClassLoader}); + } catch (IllegalAccessException e) { + throw new SurefireBooterForkException("Error obtaining test sets", e); + } catch (NoSuchMethodException e) { + throw new SurefireBooterForkException("Error obtaining test sets", e); + } catch (InvocationTargetException e) { + throw new SurefireBooterForkException(e.getTargetException().getMessage(), e.getTargetException()); + } + return testSets; + } + + private int forkSuites(List testSuites, boolean showHeading, boolean showFooter) throws SurefireBooterForkException { + Properties properties = new Properties(); + + setForkProperties(testSuites, properties); + + return fork(properties, showHeading, showFooter); + } + + private int forkSuite(Object[] testSuite, + Object testSet, + boolean showHeading, + boolean showFooter, + Properties properties) throws SurefireBooterForkException { + setForkProperties(Collections.singletonList(testSuite), properties); + + if (testSet instanceof String) { + properties.setProperty("testSet", (String)testSet); + } + + return fork(properties, showHeading, showFooter); + } + + private void setForkProperties(List testSuites, Properties properties) { + addPropertiesForTypeHolder(reports, properties, REPORT_PROPERTY_PREFIX); + addPropertiesForTypeHolder(testSuites, properties, TEST_SUITE_PROPERTY_PREFIX); + + for (int i = 0; i < classPathUrls.size(); i++) { + String url = (String)classPathUrls.get(i); + properties.setProperty("classPathUrl." + i, url); + } + + for (int i = 0; i < surefireClassPathUrls.size(); i++) { + String url = (String)surefireClassPathUrls.get(i); + properties.setProperty("surefireClassPathUrl." + i, url); + } + + properties.setProperty("childDelegation", String.valueOf(childDelegation)); + properties.setProperty("enableAssertions", String.valueOf(enableAssertions)); + properties.setProperty("useSystemClassLoader", String.valueOf(useSystemClassLoader())); + properties.setProperty("useManifestOnlyJar", String.valueOf(useManifestOnlyJar())); + properties.setProperty("failIfNoTests", String.valueOf(failIfNoTests)); + properties.setProperty("mainBundleName", mainBundleName); + } + + private File writePropertiesFile(String name, Properties properties) throws IOException { + File file = File.createTempFile(name, "tmp"); + if (!forkConfiguration.isDebug()) { + file.deleteOnExit(); + } + + writePropertiesFile(file, name, properties); + + return file; + } + + private void writePropertiesFile(File file, String name, Properties properties) throws IOException { + FileOutputStream out = new FileOutputStream(file); + + try { + properties.store(out, name); + } finally { + IOUtil.close(out); + } + } + + private void addPropertiesForTypeHolder(List typeHolderList, Properties properties, String propertyPrefix) { + for (int i = 0; i < typeHolderList.size(); i++) { + Object[] report = (Object[])typeHolderList.get(i); + + String className = (String)report[0]; + Object[] params = (Object[])report[1]; + + properties.setProperty(propertyPrefix + i, className); + + if (params != null) { + String paramProperty = convert(params[0]); + String typeProperty = params[0].getClass().getName(); + for (int j = 1; j < params.length; j++) { + paramProperty += "|"; + typeProperty += "|"; + if (params[j] != null) { + paramProperty += convert(params[j]); + typeProperty += params[j].getClass().getName(); + } + } + properties.setProperty(propertyPrefix + i + PARAMS_SUFIX, paramProperty); + properties.setProperty(propertyPrefix + i + TYPES_SUFIX, typeProperty); + } + } + } + + private static String convert(Object param) { + if (param instanceof File[]) { + File[] files = (File[])param; + return "[" + StringUtils.join(files, ",") + "]"; + } else if (param instanceof Properties) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try { + ((Properties)param).store(baos, ""); + return new String(baos.toByteArray(), "8859_1"); + } catch (Exception e) { + throw new RuntimeException("bug in property conversion", e); + } + } else { + return param.toString(); + } + } + + private final boolean useSystemClassLoader() { + return forkConfiguration.isUseSystemClassLoader() && (isForked || forkConfiguration.isForking()); + } + + private final boolean useManifestOnlyJar() { + return forkConfiguration.isUseSystemClassLoader() && forkConfiguration.isUseManifestOnlyJar(); + } + + private int fork(Properties properties, boolean showHeading, boolean showFooter) throws SurefireBooterForkException { + File surefireProperties; + File systemProperties = null; + try { + surefireProperties = writePropertiesFile("surefire", properties); + if (forkConfiguration.getSystemProperties() != null) { + systemProperties = writePropertiesFile("surefire", forkConfiguration.getSystemProperties()); + } + } catch (IOException e) { + throw new SurefireBooterForkException("Error creating properties files for forking", e); + } + + /* + System.out.println("cp: " + classPathUrls); + System.out.println("scp: " + surefireClassPathUrls); + System.out.println("sbcp: " + surefireBootClassPathUrls); + */ + + List bootClasspath = new ArrayList(surefireBootClassPathUrls.size()); + + bootClasspath.addAll(surefireBootClassPathUrls); + + /** + * For OSGi, we don't want to polute the system classpath + */ + /* + if (useSystemClassLoader()) { + bootClasspath.addAll(classPathUrls); + } + */ + + Commandline cli = + forkConfiguration.createCommandLine(bootClasspath, useManifestOnlyJar()); + + cli.createArg().setFile(surefireProperties); + + if (systemProperties != null) { + cli.createArg().setFile(systemProperties); + } + + ForkingStreamConsumer out = getForkingStreamConsumer(showHeading, showFooter, redirectTestOutputToFile); + + StreamConsumer err; + + if (redirectTestOutputToFile) { + err = out; + } else { + err = getForkingStreamConsumer(showHeading, showFooter, redirectTestOutputToFile); + } + + if (forkConfiguration.isDebug()) { + System.out.println("Forking command line: " + cli); + } + + int returnCode; + + try { + returnCode = + org.apache.maven.surefire.booter.shade.org.codehaus.plexus.util.cli.CommandLineUtils + .executeCommandLine(cli, out, err, forkedProcessTimeoutInSeconds); + } catch (org.apache.maven.surefire.booter.shade.org.codehaus.plexus.util.cli.CommandLineException e) { + throw new SurefireBooterForkException("Error while executing forked tests.", e); + } + + if (redirectTestOutputToFile) { + // ensure the FileOutputConsumerProxy flushes/closes the output file + try { + out.getOutputConsumer().testSetCompleted(); + } catch (Exception e) { + // the FileOutputConsumerProxy might throw an IllegalStateException but that's not of interest now + } + } + + if (surefireProperties != null && surefireProperties.exists()) { + FileInputStream inStream = null; + try { + inStream = new FileInputStream(surefireProperties); + + properties.load(inStream); + } catch (FileNotFoundException e) { + throw new SurefireBooterForkException("Unable to reload properties file from forked process", e); + } catch (IOException e) { + throw new SurefireBooterForkException("Unable to reload properties file from forked process", e); + } finally { + IOUtil.close(inStream); + } + } + + return returnCode; + } + + private ClassLoader createClassLoader(List classPathUrls, ClassLoader parent) throws MalformedURLException { + return createClassLoader(classPathUrls, parent, false); + } + + private ClassLoader createClassLoader(List classPathUrls, ClassLoader parent, boolean childDelegation) + throws MalformedURLException { + Set urls = new HashSet(); + + for (Iterator i = classPathUrls.iterator(); i.hasNext();) { + String url = (String)i.next(); + + if (url != null) { + File f = new File(url); + urls.add(f.toURI().toURL()); + } + } + return bundleClassLoader; + } + + private static List processStringList(String stringList) { + String sl = stringList; + + if (sl.startsWith("[") && sl.endsWith("]")) { + sl = sl.substring(1, sl.length() - 1); + } + + List list = new ArrayList(); + + String[] stringArray = StringUtils.split(sl, ","); + + for (int i = 0; i < stringArray.length; i++) { + list.add(stringArray[i].trim()); + } + return list; + } + + private static Properties loadProperties(File file) throws IOException { + Properties p = new Properties(); + + if (file != null && file.exists()) { + FileInputStream inStream = new FileInputStream(file); + try { + p.load(inStream); + } finally { + IOUtil.close(inStream); + } + } + + return p; + } + + private static void setSystemProperties(File file) throws IOException { + Properties p = loadProperties(file); + + for (Iterator i = p.keySet().iterator(); i.hasNext();) { + String key = (String)i.next(); + + System.setProperty(key, p.getProperty(key)); + } + } + + private static Object[] constructParamObjects(String paramProperty, String typeProperty) { + Object[] paramObjects = null; + if (paramProperty != null) { + // bit of a glitch that it need sto be done twice to do an odd number of vertical bars (eg |||, |||||). + String[] params = + StringUtils.split(StringUtils.replace(StringUtils.replace(paramProperty, "||", "| |"), "||", "| |"), + "|"); + String[] types = + StringUtils + .split(StringUtils.replace(StringUtils.replace(typeProperty, "||", "| |"), "||", "| |"), "|"); + + paramObjects = new Object[params.length]; + + for (int i = 0; i < types.length; i++) { + if (types[i].trim().length() == 0) { + params[i] = null; + } else if (types[i].equals(String.class.getName())) { + paramObjects[i] = params[i]; + } else if (types[i].equals(File.class.getName())) { + paramObjects[i] = new File(params[i]); + } else if (types[i].equals(File[].class.getName())) { + List stringList = processStringList(params[i]); + File[] fileList = new File[stringList.size()]; + for (int j = 0; j < stringList.size(); j++) { + fileList[j] = new File((String)stringList.get(j)); + } + paramObjects[i] = fileList; + } else if (types[i].equals(ArrayList.class.getName())) { + paramObjects[i] = processStringList(params[i]); + } else if (types[i].equals(Boolean.class.getName())) { + paramObjects[i] = Boolean.valueOf(params[i]); + } else if (types[i].equals(Integer.class.getName())) { + paramObjects[i] = Integer.valueOf(params[i]); + } else if (types[i].equals(Properties.class.getName())) { + final Properties result = new Properties(); + final String value = params[i]; + try { + ByteArrayInputStream bais = new ByteArrayInputStream(value.getBytes("8859_1")); + result.load(bais); + } catch (Exception e) { + throw new RuntimeException("bug in property conversion", e); + } + paramObjects[i] = result; + } else { + // TODO: could attempt to construct with a String constructor if needed + throw new IllegalArgumentException("Unknown parameter type: " + types[i]); + } + } + } + return paramObjects; + } + + /** + * This method is invoked when Surefire is forked - this method parses and organizes the arguments passed to it and + * then calls the Surefire class' run method.

The system exit code will be 1 if an exception is thrown. + * + * @param args + */ + public static void main(String[] args) throws Throwable { + // noinspection CatchGenericClass,OverlyBroadCatchBlock + try { + if (args.length > 1) { + setSystemProperties(new File(args[1])); + } + + File surefirePropertiesFile = new File(args[0]); + Properties p = loadProperties(surefirePropertiesFile); + + SortedMap classPathUrls = new TreeMap(); + + SortedMap surefireClassPathUrls = new TreeMap(); + + OSGiSurefireBooter surefireBooter = new OSGiSurefireBooter(true); + + ForkConfiguration forkConfiguration = new ForkConfiguration(); + forkConfiguration.setForkMode("never"); + surefireBooter.setForkConfiguration(forkConfiguration); + + for (Enumeration e = p.propertyNames(); e.hasMoreElements();) { + String name = (String)e.nextElement(); + + if (name.startsWith(REPORT_PROPERTY_PREFIX) && !name.endsWith(PARAMS_SUFIX) + && !name.endsWith(TYPES_SUFIX)) { + String className = p.getProperty(name); + + String params = p.getProperty(name + PARAMS_SUFIX); + String types = p.getProperty(name + TYPES_SUFIX); + surefireBooter.addReport(className, constructParamObjects(params, types)); + } else if (name.startsWith(TEST_SUITE_PROPERTY_PREFIX) && !name.endsWith(PARAMS_SUFIX) + && !name.endsWith(TYPES_SUFIX)) { + String className = p.getProperty(name); + + String params = p.getProperty(name + PARAMS_SUFIX); + String types = p.getProperty(name + TYPES_SUFIX); + surefireBooter.addTestSuite(className, constructParamObjects(params, types)); + } else if (name.startsWith("classPathUrl.")) { + classPathUrls.put(Integer.valueOf(name.substring(name.indexOf('.') + 1)), p.getProperty(name)); + } else if (name.startsWith("surefireClassPathUrl.")) { + surefireClassPathUrls.put(Integer.valueOf(name.substring(name.indexOf('.') + 1)), p + .getProperty(name)); + } else if (name.startsWith("surefireBootClassPathUrl.")) { + surefireBooter.addSurefireBootClassPathUrl(p.getProperty(name)); + } else if ("childDelegation".equals(name)) { + surefireBooter.childDelegation = Boolean.valueOf(p.getProperty("childDelegation")).booleanValue(); + } else if ("enableAssertions".equals(name)) { + surefireBooter.enableAssertions = Boolean.valueOf(p.getProperty("enableAssertions")).booleanValue(); + } else if ("useSystemClassLoader".equals(name)) { + surefireBooter.forkConfiguration.setUseSystemClassLoader(Boolean.valueOf(p + .getProperty("useSystemClassLoader")).booleanValue()); + } else if ("useManifestOnlyJar".equals(name)) { + surefireBooter.forkConfiguration.setUseManifestOnlyJar(Boolean.valueOf(p + .getProperty("useManifestOnlyJar")).booleanValue()); + } else if ("failIfNoTests".equals(name)) { + surefireBooter.setFailIfNoTests(Boolean.valueOf(p.getProperty("failIfNoTests")).booleanValue()); + } else if ("mainBundleName".equals(name)) { + surefireBooter.setMainBundleName(p.getProperty("mainBundleName")); + } + } + + for (Iterator cpi = classPathUrls.keySet().iterator(); cpi.hasNext();) { + String url = (String)classPathUrls.get(cpi.next()); + surefireBooter.addClassPathUrl(url); + } + + for (Iterator scpi = surefireClassPathUrls.keySet().iterator(); scpi.hasNext();) { + String url = (String)surefireClassPathUrls.get(scpi.next()); + surefireBooter.addSurefireClassPathUrl(url); + } + + String testSet = p.getProperty("testSet"); + int result; + if (testSet != null) { + result = surefireBooter.runSuitesInProcess(testSet, p); + } else { + result = surefireBooter.runSuitesInProcess(); + } + + surefireBooter.writePropertiesFile(surefirePropertiesFile, "surefire", p); + + // noinspection CallToSystemExit + System.exit(result); + } catch (Throwable t) { + // Just throwing does getMessage() and a local trace - we want to call printStackTrace for a full trace + // noinspection UseOfSystemOutOrSystemErr + t.printStackTrace(System.err); + // noinspection ProhibitedExceptionThrown,CallToSystemExit + System.exit(1); + } + } + + public void setChildDelegation(boolean childDelegation) { + this.childDelegation = childDelegation; + } + + private ForkingStreamConsumer getForkingStreamConsumer(boolean showHeading, + boolean showFooter, + boolean redirectTestOutputToFile) { + OutputConsumer outputConsumer = new StandardOutputConsumer(); + + if (redirectTestOutputToFile) { + outputConsumer = new FileOutputConsumerProxy(outputConsumer, getReportsDirectory()); + } + + if (!showHeading) { + outputConsumer = new SupressHeaderOutputConsumerProxy(outputConsumer); + } + if (!showFooter) { + outputConsumer = new SupressFooterOutputConsumerProxy(outputConsumer); + } + + return new ForkingStreamConsumer(outputConsumer); + } + + public void setEnableAssertions(boolean enableAssertions) { + this.enableAssertions = enableAssertions; + } + + public void setForkedProcessTimeoutInSeconds(int forkedProcessTimeoutInSeconds) { + this.forkedProcessTimeoutInSeconds = forkedProcessTimeoutInSeconds; + } + + private String mainBundleName; + + public void setMainBundleName(String mainBundleName) { + this.mainBundleName = mainBundleName; + } + + private Object host; + private BundleClassLoader bundleClassLoader; + + public Object start() throws Exception { + Set urls = new HashSet(); + + // Merge the two classpaths so that all of them will be OSGi-enabled + Set cps = new HashSet(classPathUrls); + cps.addAll(surefireClassPathUrls); + + for (String url: cps) { + if (url != null) { + File f = new File(url); + try { + urls.add(f.toURI().toURL()); + } catch (MalformedURLException e) { + e.printStackTrace(); + } + } + } + + host = createEquinox(urls); + BundleContext context = startEquinox(host); + Bundle mainBundle = null; + for (Bundle bundle : context.getBundles()) { + // Fragement bundle cannot be used to load class, use the main bundle + if (mainBundleName.equals(bundle.getSymbolicName())) { + mainBundle = bundle; + break; + } + } + + bundleClassLoader = new BundleClassLoader(mainBundle, null); + if (assertionStatusMethod != null) { + try { + Object[] args = new Object[] {enableAssertions ? Boolean.TRUE : Boolean.FALSE}; + assertionStatusMethod.invoke(bundleClassLoader, args); + } catch (IllegalAccessException e) { + throw new NestedRuntimeException("Unable to access the assertion enablement method", e); + } catch (InvocationTargetException e) { + throw new NestedRuntimeException("Unable to invoke the assertion enablement method", e); + } + } + + return host; + } + + public void stop() { + if (host != null) { + try { + stopEquinox(host); + } catch (Exception e) { + throw new IllegalStateException(e); + } + host = null; + bundleClassLoader = null; + } + } + + private static Object createEquinox(Set urls) throws Exception { + String name = "org.apache.tuscany.sca.node.equinox.launcher.EquinoxHost"; + Class cls = Class.forName(name); + Constructor ctor = cls.getConstructor(Set.class); + return ctor.newInstance(urls); + } + + private static BundleContext startEquinox(Object host) throws Exception { + Method start = host.getClass().getMethod("start"); + return (BundleContext)start.invoke(host); + } + + private static void stopEquinox(Object host) throws Exception { + Method stop = host.getClass().getMethod("stop"); + stop.invoke(host); + } + + /** + * Returns a string representation of the given bundle. + * + * @param b + * @param verbose + * @return + */ + static String string(Bundle bundle, boolean verbose) { + StringBuffer sb = new StringBuffer(); + sb.append(bundle.getBundleId()).append(" ").append(bundle.getSymbolicName()); + int s = bundle.getState(); + if ((s & Bundle.UNINSTALLED) != 0) { + sb.append(" UNINSTALLED"); + } + if ((s & Bundle.INSTALLED) != 0) { + sb.append(" INSTALLED"); + } + if ((s & Bundle.RESOLVED) != 0) { + sb.append(" RESOLVED"); + } + if ((s & Bundle.STARTING) != 0) { + sb.append(" STARTING"); + } + if ((s & Bundle.STOPPING) != 0) { + sb.append(" STOPPING"); + } + if ((s & Bundle.ACTIVE) != 0) { + sb.append(" ACTIVE"); + } + + if (verbose) { + sb.append(" ").append(bundle.getLocation()); + sb.append(" ").append(bundle.getHeaders()); + } + return sb.toString(); + } + +} diff --git a/maven-plugins/tags/maven-osgi-junit-plugin-1.0/src/main/java/org/apache/tuscany/maven/plugin/surefire/OSGiSurefirePlugin.java b/maven-plugins/tags/maven-osgi-junit-plugin-1.0/src/main/java/org/apache/tuscany/maven/plugin/surefire/OSGiSurefirePlugin.java new file mode 100644 index 0000000000..90e5911f42 --- /dev/null +++ b/maven-plugins/tags/maven-osgi-junit-plugin-1.0/src/main/java/org/apache/tuscany/maven/plugin/surefire/OSGiSurefirePlugin.java @@ -0,0 +1,1310 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.tuscany.maven.plugin.surefire; + +import static org.osgi.framework.Constants.BUNDLE_MANIFESTVERSION; +import static org.osgi.framework.Constants.BUNDLE_NAME; +import static org.osgi.framework.Constants.BUNDLE_SYMBOLICNAME; +import static org.osgi.framework.Constants.BUNDLE_VERSION; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.jar.Attributes; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; +import java.util.zip.ZipEntry; + +import org.apache.maven.artifact.Artifact; +import org.apache.maven.artifact.ArtifactUtils; +import org.apache.maven.artifact.factory.ArtifactFactory; +import org.apache.maven.artifact.metadata.ArtifactMetadataSource; +import org.apache.maven.artifact.repository.ArtifactRepository; +import org.apache.maven.artifact.resolver.ArtifactNotFoundException; +import org.apache.maven.artifact.resolver.ArtifactResolutionException; +import org.apache.maven.artifact.resolver.ArtifactResolutionResult; +import org.apache.maven.artifact.resolver.ArtifactResolver; +import org.apache.maven.artifact.resolver.filter.ArtifactFilter; +import org.apache.maven.artifact.resolver.filter.ExcludesArtifactFilter; +import org.apache.maven.artifact.versioning.ArtifactVersion; +import org.apache.maven.artifact.versioning.DefaultArtifactVersion; +import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException; +import org.apache.maven.artifact.versioning.VersionRange; +import org.apache.maven.execution.MavenSession; +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugin.logging.Log; +import org.apache.maven.project.MavenProject; +import org.apache.maven.surefire.booter.SurefireBooterForkException; +import org.apache.maven.surefire.booter.SurefireExecutionException; +import org.apache.maven.surefire.booter.shade.org.codehaus.plexus.util.StringUtils; +import org.apache.maven.surefire.report.BriefConsoleReporter; +import org.apache.maven.surefire.report.BriefFileReporter; +import org.apache.maven.surefire.report.ConsoleReporter; +import org.apache.maven.surefire.report.DetailedConsoleReporter; +import org.apache.maven.surefire.report.FileReporter; +import org.apache.maven.surefire.report.ForkingConsoleReporter; +import org.apache.maven.surefire.report.XMLReporter; +import org.apache.maven.toolchain.Toolchain; +import org.apache.maven.toolchain.ToolchainManager; +import org.codehaus.plexus.component.repository.exception.ComponentLookupException; +import org.osgi.framework.Constants; +import org.osgi.framework.Version; + +/** + * Derived from maven-surefire-plugin 2.4.3 + * Run tests using Surefire. + * + * @requiresDependencyResolution test + * @goal test + * @phase test + */ +public class OSGiSurefirePlugin extends AbstractMojo { + + /** + * Set this to 'true' to skip running tests, but still compile them. Its use is NOT RECOMMENDED, but quite + * convenient on occasion. + * + * @parameter expression="${skipTests}" + * @since 2.4 + */ + protected boolean skipTests; + + /** + * DEPRECATED This old parameter is just like skipTests, but bound to the old property maven.test.skip.exec. + * Use -DskipTests instead; it's shorter. + * + * @deprecated + * @parameter expression="${maven.test.skip.exec}" + * @since 2.3 + */ + protected boolean skipExec; + + /** + * Set this to 'true' to bypass unit tests entirely. Its use is NOT RECOMMENDED, especially if you + * enable it using the "maven.test.skip" property, because maven.test.skip disables both running the + * tests and compiling the tests. Consider using the skipTests parameter instead. + * + * @parameter expression="${maven.test.skip}" + */ + protected boolean skip; + + /** + * Set this to true to ignore a failure during testing. Its use is NOT RECOMMENDED, but quite convenient on + * occasion. + * + * @parameter expression="${maven.test.failure.ignore}" + */ + protected boolean testFailureIgnore; + + /** + * The base directory of the project being tested. This can be obtained in your unit test by + * System.getProperty("basedir"). + * + * @parameter expression="${basedir}" + * @required + */ + protected File basedir; + + /** + * The directory containing generated test classes of the project being tested. + * + * @parameter expression="${project.build.testOutputDirectory}" + * @required + */ + protected File testClassesDirectory; + + /** + * The directory containing generated classes of the project being tested. + * + * @parameter expression="${project.build.outputDirectory}" + * @required + */ + protected File classesDirectory; + + /** + * The Maven Project Object + * + * @parameter expression="${project}" + * @required + * @readonly + */ + protected MavenProject project; + + /** + * The classpath elements of the project being tested. + * + * @parameter expression="${project.testClasspathElements}" + * @required + * @readonly + */ + protected List classpathElements; + + /** + * Additional elements to be appended to the classpath. + * + * @parameter + * @since 2.4 + */ + protected List additionalClasspathElements; + + /** + * Base directory where all reports are written to. + * + * @parameter expression="${project.build.directory}/surefire-osgi-reports" + */ + protected File reportsDirectory; + + /** + * The test source directory containing test class sources. + * + * @parameter expression="${project.build.testSourceDirectory}" + * @required + * @since 2.2 + */ + protected File testSourceDirectory; + + /** + * Specify this parameter to run individual tests by file name, overriding the includes/excludes + * parameters. Each pattern you specify here will be used to create an + * include pattern formatted like **/${test}.java, so you can just type "-Dtest=MyTest" + * to run a single test called "foo/MyTest.java". This parameter will override the TestNG suiteXmlFiles + * parameter. + * + * @parameter expression="${test}" + */ + protected String test; + + /** + * List of patterns (separated by commas) used to specify the tests that should be included in testing. When not + * specified and when the test parameter is not specified, the default includes will be + * **/Test*.java **/*Test.java **/*TestCase.java. This parameter is ignored if + * TestNG suiteXmlFiles are specified. + * + * @parameter + */ + protected List includes; + + /** + * List of patterns (separated by commas) used to specify the tests that should be excluded in testing. When not + * specified and when the test parameter is not specified, the default excludes will be + * **/*$* (which excludes all inner classes). This parameter is ignored if + * TestNG suiteXmlFiles are specified. + * + * @parameter + */ + protected List excludes; + + /** + * ArtifactRepository of the localRepository. To obtain the directory of localRepository in unit tests use + * System.setProperty( "localRepository"). + * + * @parameter expression="${localRepository}" + * @required + * @readonly + */ + protected ArtifactRepository localRepository; + + /** + * List of System properties to pass to the JUnit tests. + * + * @parameter + */ + protected Properties systemProperties; + + /** + * List of properties for configuring all TestNG related configurations. This is the new + * preferred method of configuring TestNG. + * + * @parameter + * @since 2.4 + */ + protected Properties properties; + + /** + * Map of of plugin artifacts. + * + * @parameter expression="${plugin.artifactMap}" + * @required + * @readonly + */ + protected Map pluginArtifactMap; + + /** + * @parameter expression="${plugin.groupId}" + * @required + * @readonly + */ + protected String pluginGroupId; + /** + * @parameter expression="${plugin.artifactId}" + * @required + * @readonly + */ + protected String pluginArtifactId; + /** + * @parameter expression="${plugin.version}" + * @required + * @readonly + */ + protected String pluginVersion; + + /** + * Map of of project artifacts. + * + * @parameter expression="${project.artifactMap}" + * @required + * @readonly + */ + protected Map projectArtifactMap; + + /** + * Option to print summary of test suites or just print the test cases that has errors. + * + * @parameter expression="${surefire.printSummary}" default-value="true" + */ + protected boolean printSummary; + + /** + * Selects the formatting for the test report to be generated. Can be set as brief or plain. + * + * @parameter expression="${surefire.reportFormat}" default-value="brief" + */ + protected String reportFormat; + + /** + * Option to generate a file test report or just output the test report to the console. + * + * @parameter expression="${surefire.useFile}" default-value="true" + */ + protected boolean useFile; + + /** + * When forking, set this to true to redirect the unit test standard output to a file (found in + * reportsDirectory/testName-output.txt). + * + * @parameter expression="${maven.test.redirectTestOutputToFile}" default-value="false" + * @since 2.3 + */ + protected boolean redirectTestOutputToFile; + + /** + * Set this to "true" to cause a failure if there are no tests to run. Defaults to false. + * + * @parameter expression="${failIfNoTests}" + * @since 2.4 + */ + protected Boolean failIfNoTests; + + /** + * Option to specify the forking mode. Can be "never", "once" or "always". "none" and "pertest" are also accepted + * for backwards compatibility. + * + * @parameter expression="${forkMode}" default-value="once" + * @since 2.1 + */ + protected String forkMode; + + /** + * Option to specify the jvm (or path to the java executable) to use with the forking options. For the default, the + * jvm will be the same as the one used to run Maven. + * + * @parameter expression="${jvm}" + * @since 2.1 + */ + protected String jvm; + + /** + * Arbitrary JVM options to set on the command line. + * + * @parameter expression="${argLine}" + * @since 2.1 + */ + protected String argLine; + + /** + * Attach a debugger to the forked JVM. If set to "true", the process will suspend and + * wait for a debugger to attach on port 5005. If set to some other string, that + * string will be appended to the argLine, allowing you to configure arbitrary + * debuggability options (without overwriting the other options specified in the argLine). + * + * @parameter expression="${maven.surefire.debug}" + * @since 2.4 + */ + protected String debugForkedProcess; + + /** + * Kill the forked test process after a certain number of seconds. If set to 0, + * wait forever for the process, never timing out. + * + * @parameter expression="${surefire.timeout}" + * @since 2.4 + */ + protected int forkedProcessTimeoutInSeconds; + + /** + * Additional environments to set on the command line. + * + * @parameter + * @since 2.1.3 + */ + protected Map environmentVariables = new HashMap(); + + /** + * Command line working directory. + * + * @parameter expression="${basedir}" + * @since 2.1.3 + */ + protected File workingDirectory; + + /** + * When false it makes tests run using the standard classloader delegation instead of the default Maven isolated + * classloader. Only used when forking (forkMode is not "none").
Setting it to false helps with some problems + * caused by conflicts between xml parsers in the classpath and the Java 5 provider parser. + * + * @parameter expression="${childDelegation}" default-value="false" + * @since 2.1 + */ + protected boolean childDelegation; + + /** + * (TestNG only) Groups for this test. Only classes/methods/etc decorated with one of the groups specified here will be included + * in test run, if specified. This parameter is overridden if suiteXmlFiles are specified. + * + * @parameter expression="${groups}" + * @since 2.2 + */ + protected String groups; + + /** + * (TestNG only) Excluded groups. Any methods/classes/etc with one of the groups specified in this list will specifically not be + * run. This parameter is overridden if suiteXmlFiles are specified. + * + * @parameter expression="${excludedGroups}" + * @since 2.2 + */ + protected String excludedGroups; + + /** + * (TestNG only) List of TestNG suite xml file locations, seperated by commas. Note that suiteXmlFiles is incompatible + * with several other parameters on this plugin, like includes/excludes. This parameter is ignored if + * the "test" parameter is specified (allowing you to run a single test instead of an entire suite). + * + * @parameter + * @since 2.2 + */ + protected File[] suiteXmlFiles; + + /** + * Allows you to specify the name of the JUnit artifact. If not set, junit:junit will be used. + * + * @parameter expression="${junitArtifactName}" default-value="junit:junit" + * @since 2.3.1 + */ + protected String junitArtifactName; + + /** + * Allows you to specify the name of the TestNG artifact. If not set, org.testng:testng will be used. + * + * @parameter expression="${testNGArtifactName}" default-value="org.testng:testng" + * @since 2.3.1 + */ + protected String testNGArtifactName; + + /** + * (TestNG only) The attribute thread-count allows you to specify how many threads should be allocated for this execution. Only + * makes sense to use in conjunction with parallel. + * + * @parameter expression="${threadCount}" + * @since 2.2 + */ + protected int threadCount; + + /** + * (TestNG only) When you use the parallel attribute, TestNG will try to run all your test methods in separate threads, except for + * methods that depend on each other, which will be run in the same thread in order to respect their order of + * execution. + * + * @parameter expression="${parallel}" + * @todo test how this works with forking, and console/file output parallelism + * @since 2.2 + */ + protected String parallel; + + /** + * Whether to trim the stack trace in the reports to just the lines within the test, or show the full trace. + * + * @parameter expression="${trimStackTrace}" default-value="true" + * @since 2.2 + */ + protected boolean trimStackTrace; + + /** + * Resolves the artifacts needed. + * + * @component + */ + protected ArtifactResolver artifactResolver; + + /** + * Creates the artifact + * + * @component + */ + protected ArtifactFactory artifactFactory; + + /** + * The plugin remote repositories declared in the pom. + * + * @parameter expression="${project.pluginArtifactRepositories}" + * @since 2.2 + */ + protected List remoteRepositories; + + /** + * For retrieval of artifact's metadata. + * + * @component + */ + protected ArtifactMetadataSource metadataSource; + + protected static final String BRIEF_REPORT_FORMAT = "brief"; + + protected static final String PLAIN_REPORT_FORMAT = "plain"; + + protected Properties originalSystemProperties; + + /** + * Flag to disable the generation of report files in xml format. + * + * @parameter expression="${disableXmlReport}" default-value="false" + * @since 2.2 + */ + protected boolean disableXmlReport; + + /** + * Option to pass dependencies to the system's classloader instead of using an isolated class loader when forking. + * Prevents problems with JDKs which implement the service provider lookup mechanism by using the system's + * classloader. Default value is "true". + * + * @parameter expression="${surefire.useSystemClassLoader}" + * @since 2.3 + */ + protected Boolean useSystemClassLoader; + + /** + * By default, Surefire forks your tests using a manifest-only jar; set this parameter + * to "false" to force it to launch your tests with a plain old Java classpath. + * (See http://maven.apache.org/plugins/maven-surefire-plugin/examples/class-loading.html + * for a more detailed explanation of manifest-only jars and their benefits.) + * + * Default value is "true". Beware, setting this to "false" may cause your tests to + * fail on Windows if your classpath is too long. + * + * @parameter expression="${surefire.useManifestOnlyJar}" default-value="true" + * @since 2.4.3 + */ + protected boolean useManifestOnlyJar; + + /** + * By default, Surefire enables JVM assertions for the execution of your test cases. To disable the assertions, set + * this flag to false. + * + * @parameter expression="${enableAssertions}" default-value="true" + * @since 2.3.1 + */ + protected boolean enableAssertions; + + /** + * The current build session instance. + * + * @parameter expression="${session}" + * @required + * @readonly + */ + protected MavenSession session; + + public void execute() throws MojoExecutionException, MojoFailureException { + if (project.getPackaging().equals("pom")) { + return; + } + + if (verifyParameters()) { + OSGiSurefireBooter surefireBooter = constructSurefireBooter(); + + Log log = getLog(); + Set jarFiles = new HashSet(); + + /* + for (Object o : project.getArtifacts()) { + Artifact a = (Artifact)o; + if ("pom".equals(a.getType())) { + // Skip pom projects + continue; + } + try { + if (log.isDebugEnabled()) { + log.debug("Adding: " + a); + } + jarFiles.add(a.getFile().toURI().toURL()); + } catch (MalformedURLException e) { + getLog().error(e); + } + } + */ + + /* + * Add org.apache.tuscany.sca:tuscany-extensibility-osgi module + */ + Artifact ext = getArtifact("org.apache.tuscany.sca", "tuscany-extensibility-equinox"); + if (log.isDebugEnabled()) { + log.debug("Adding: " + ext); + } + jarFiles.add(ext.getFile().getAbsolutePath()); + + Artifact con = getArtifact("org.apache.tuscany.sca", "tuscany-contribution-osgi"); + if (log.isDebugEnabled()) { + log.debug("Adding: " + con); + } + jarFiles.add(con.getFile().getAbsolutePath()); + + String name = project.getBuild().getFinalName(); + String mainBundleName = null; + File mainJar = new File(project.getBuild().getDirectory(), name + "-osgi.jar"); + File testJar = new File(project.getBuild().getDirectory(), name + "-osgi-tests.jar"); + try { + Manifest manifest = createMainBundle(); + mainBundleName = manifest.getMainAttributes().getValue(BUNDLE_SYMBOLICNAME); + int sc = mainBundleName.indexOf(';'); + if (sc != -1) { + mainBundleName = mainBundleName.substring(0, sc); + } + generateJar(classesDirectory, mainJar, manifest); + Manifest testManifest = createTestFragment(manifest); + generateJar(testClassesDirectory, testJar, testManifest); + jarFiles.add(mainJar.getAbsolutePath()); + jarFiles.add(testJar.getAbsolutePath()); + } catch (IOException e) { + getLog().error(e); + } + + if (log.isDebugEnabled()) { + log.debug("Main bundle: " + mainBundleName); + } + surefireBooter.setMainBundleName(mainBundleName); + for (String url : jarFiles) { + surefireBooter.addClassPathUrl(url); + } + + getLog().info("Surefire report directory: " + reportsDirectory); + + int result; + try { + result = surefireBooter.run(); + } catch (SurefireBooterForkException e) { + throw new MojoExecutionException(e.getMessage(), e); + } catch (SurefireExecutionException e) { + throw new MojoExecutionException(e.getMessage(), e); + } + + if (originalSystemProperties != null && !surefireBooter.isForking()) { + // restore system properties, only makes sense when not forking.. + System.setProperties(originalSystemProperties); + } + + if (result == 0) + return; + + String msg; + + if (result == OSGiSurefireBooter.NO_TESTS_EXIT_CODE) { + if ((failIfNoTests == null) || !failIfNoTests.booleanValue()) + return; + // TODO: i18n + throw new MojoFailureException( + "No tests were executed! (Set -DfailIfNoTests=false to ignore this error.)"); + } else { + // TODO: i18n + msg = + "There are test failures.\n\nPlease refer to " + reportsDirectory + + " for the individual test results."; + + } + + if (testFailureIgnore) { + getLog().error(msg); + } else { + throw new MojoFailureException(msg); + } + } + } + + protected boolean verifyParameters() throws MojoFailureException { + if (skip || skipTests || skipExec) { + getLog().info("Tests are skipped."); + return false; + } + + if (!testClassesDirectory.exists()) { + if (failIfNoTests != null && failIfNoTests.booleanValue()) { + throw new MojoFailureException("No tests to run!"); + } + getLog().info("No tests to run."); + return false; + } + + if (useSystemClassLoader != null && ForkConfiguration.FORK_NEVER.equals(forkMode)) { + getLog().warn("useSystemClassloader setting has no effect when not forking"); + } + + return true; + } + + /** + * Converts old TestNG configuration parameters over to new properties based configuration + * method. (if any are defined the old way) + */ + private void convertTestNGParameters() { + if (properties == null) { + properties = new Properties(); + } + + if (this.parallel != null) { + properties.setProperty("parallel", this.parallel); + } + if (this.excludedGroups != null) { + properties.setProperty("excludegroups", this.excludedGroups); + } + if (this.groups != null) { + properties.setProperty("groups", this.groups); + } + + if (this.threadCount > 0) { + properties.setProperty("threadcount", new Integer(this.threadCount).toString()); + } + } + + private OSGiSurefireBooter constructSurefireBooter() throws MojoExecutionException, MojoFailureException { + OSGiSurefireBooter surefireBooter = new OSGiSurefireBooter(); + + // Build up the surefire boot classpath + // * org.apache.tuscany.sca:tuscany-maven-surefire-osgi-plugin (non-transitive + // to exclude maven dependencies + // * org.apache.tuscany.sca:tuscany-node-launcher-equinox (transitive) + // * org.apache.maven.surefire:surefire-booter (transitive) + // Get the artifact for the OSGi surefire plugin + Artifact osgiArtifact = + artifactFactory.createArtifact(pluginGroupId, + pluginArtifactId, + pluginVersion, + Artifact.SCOPE_TEST, + "maven-plugin"); + try { + artifactResolver.resolve(osgiArtifact, remoteRepositories, localRepository); + surefireBooter.addSurefireBootClassPathUrl(osgiArtifact.getFile().getAbsolutePath()); + } catch (Exception e) { + throw new MojoExecutionException("Unable to resolve " + osgiArtifact); + } + + Artifact launcher = (Artifact) pluginArtifactMap.get("org.apache.tuscany.sca:tuscany-node-launcher-equinox"); + + // Look up the surefire-booter + Artifact surefireArtifact = (Artifact)pluginArtifactMap.get("org.apache.maven.surefire:surefire-booter"); + if (surefireArtifact == null) { + throw new MojoExecutionException("Unable to locate surefire-booter in the list of plugin artifacts"); + } + + surefireArtifact.isSnapshot(); // TODO: this is ridiculous, but it fixes getBaseVersion to be -SNAPSHOT if + // needed + + Artifact junitArtifact; + Artifact testNgArtifact; + try { + addArtifact(surefireBooter, surefireArtifact); + addArtifact(surefireBooter, launcher); + + junitArtifact = (Artifact)projectArtifactMap.get(junitArtifactName); + // SUREFIRE-378, junit can have an alternate artifact name + if (junitArtifact == null && "junit:junit".equals(junitArtifactName)) { + junitArtifact = (Artifact)projectArtifactMap.get("junit:junit-dep"); + } + + // TODO: this is pretty manual, but I'd rather not require the plugin > dependencies section right now + testNgArtifact = (Artifact)projectArtifactMap.get(testNGArtifactName); + + if (testNgArtifact != null) { + VersionRange range = VersionRange.createFromVersionSpec("[4.7,)"); + if (!range.containsVersion(new DefaultArtifactVersion(testNgArtifact.getVersion()))) { + throw new MojoFailureException( + "TestNG support requires version 4.7 or above. You have declared version " + testNgArtifact + .getVersion()); + } + + convertTestNGParameters(); + + if (this.testClassesDirectory != null) { + properties.setProperty("testng.test.classpath", testClassesDirectory.getAbsolutePath()); + } + + addArtifact(surefireBooter, testNgArtifact); + + // The plugin uses a JDK based profile to select the right testng. We might be explicity using a + // different one since its based on the source level, not the JVM. Prune using the filter. + addProvider(surefireBooter, "surefire-testng", surefireArtifact.getBaseVersion(), testNgArtifact); + } else if (junitArtifact != null && junitArtifact.getBaseVersion().startsWith("4")) { + addProvider(surefireBooter, "surefire-junit4", surefireArtifact.getBaseVersion(), null); + } else { + // add the JUnit provider as default - it doesn't require JUnit to be present, + // since it supports POJO tests. + addProvider(surefireBooter, "surefire-junit", surefireArtifact.getBaseVersion(), null); + } + } catch (ArtifactNotFoundException e) { + throw new MojoExecutionException("Unable to locate required surefire provider dependency: " + e + .getMessage(), e); + } catch (InvalidVersionSpecificationException e) { + throw new MojoExecutionException("Error determining the TestNG version requested: " + e.getMessage(), e); + } catch (ArtifactResolutionException e) { + throw new MojoExecutionException("Error to resolving surefire provider dependency: " + e.getMessage(), e); + } + + if (suiteXmlFiles != null && suiteXmlFiles.length > 0 && test == null) { + if (testNgArtifact == null) { + throw new MojoExecutionException("suiteXmlFiles is configured, but there is no TestNG dependency"); + } + + // TODO: properties should be passed in here too + surefireBooter.addTestSuite("org.apache.maven.surefire.testng.TestNGXmlTestSuite", + new Object[] {suiteXmlFiles, testSourceDirectory.getAbsolutePath(), + testNgArtifact.getVersion(), testNgArtifact.getClassifier(), + properties, reportsDirectory}); + } else { + List includes; + List excludes; + + if (test != null) { + // Check to see if we are running a single test. The raw parameter will + // come through if it has not been set. + + // FooTest -> **/FooTest.java + + includes = new ArrayList(); + + excludes = new ArrayList(); + + if (failIfNoTests == null) { + failIfNoTests = Boolean.TRUE; + } + + String[] testRegexes = StringUtils.split(test, ","); + + for (int i = 0; i < testRegexes.length; i++) { + String testRegex = testRegexes[i]; + if (testRegex.endsWith(".java")) { + testRegex = testRegex.substring(0, testRegex.length() - 5); + } + // Allow paths delimited by '.' or '/' + testRegex = testRegex.replace('.', '/'); + includes.add("**/" + testRegex + ".java"); + } + } else { + includes = this.includes; + + excludes = this.excludes; + + // defaults here, qdox doesn't like the end javadoc value + // Have to wrap in an ArrayList as surefire expects an ArrayList instead of a List for some reason + if (includes == null || includes.size() == 0) { + includes = + new ArrayList(Arrays + .asList(new String[] {"**/Test*.java", "**/*Test.java", "**/*TestCase.java"})); + } + if (excludes == null || excludes.size() == 0) { + excludes = new ArrayList(Arrays.asList(new String[] {"**/*$*"})); + } + } + + if (testNgArtifact != null) { + surefireBooter.addTestSuite("org.apache.maven.surefire.testng.TestNGDirectoryTestSuite", + new Object[] {testClassesDirectory, includes, excludes, + testSourceDirectory.getAbsolutePath(), + testNgArtifact.getVersion(), testNgArtifact.getClassifier(), + properties, reportsDirectory}); + } else { + String junitDirectoryTestSuite; + if (junitArtifact != null && junitArtifact.getBaseVersion() != null + && junitArtifact.getBaseVersion().startsWith("4")) { + junitDirectoryTestSuite = "org.apache.maven.surefire.junit4.JUnit4DirectoryTestSuite"; + } else { + junitDirectoryTestSuite = "org.apache.maven.surefire.junit.JUnitDirectoryTestSuite"; + } + + // fall back to JUnit, which also contains POJO support. Also it can run + // classes compiled against JUnit since it has a dependency on JUnit itself. + surefireBooter.addTestSuite(junitDirectoryTestSuite, new Object[] {testClassesDirectory, includes, + excludes}); + } + } + + // ---------------------------------------------------------------------- + // + // ---------------------------------------------------------------------- + + getLog().debug("Test Classpath :"); + + classpathElements.remove(classesDirectory.getAbsolutePath()); + classpathElements.remove(testClassesDirectory.getAbsolutePath()); + /* + // Check if we need to add configured classes/test classes directories here. + // If they are configured, we should remove the default to avoid conflicts. + if (!project.getBuild().getOutputDirectory().equals(classesDirectory.getAbsolutePath())) { + classpathElements.remove(project.getBuild().getOutputDirectory()); + classpathElements.add(classesDirectory.getAbsolutePath()); + } + if (!project.getBuild().getTestOutputDirectory().equals(testClassesDirectory.getAbsolutePath())) { + classpathElements.remove(project.getBuild().getTestOutputDirectory()); + classpathElements.add(testClassesDirectory.getAbsolutePath()); + } + */ + + for (Iterator i = classpathElements.iterator(); i.hasNext();) { + String classpathElement = (String)i.next(); + + getLog().debug(" " + classpathElement); + + surefireBooter.addClassPathUrl(classpathElement); + } + + Toolchain tc = getToolchain(); + + if (tc != null) { + getLog().info("Toolchain in surefire-plugin: " + tc); + if (ForkConfiguration.FORK_NEVER.equals(forkMode)) { + forkMode = ForkConfiguration.FORK_ONCE; + } + if (jvm != null) { + getLog().warn("Toolchains are ignored, 'executable' parameter is set to " + jvm); + } else { + jvm = tc.findTool("java"); //NOI18N + } + } + + if (additionalClasspathElements != null) { + for (Iterator i = additionalClasspathElements.iterator(); i.hasNext();) { + String classpathElement = (String)i.next(); + + getLog().debug(" " + classpathElement); + + surefireBooter.addClassPathUrl(classpathElement); + } + } + + // ---------------------------------------------------------------------- + // Forking + // ---------------------------------------------------------------------- + + ForkConfiguration fork = new ForkConfiguration(); + + fork.setForkMode(forkMode); + + processSystemProperties(!fork.isForking()); + + if (getLog().isDebugEnabled()) { + showMap(systemProperties, "system property"); + } + + if (fork.isForking()) { + useSystemClassLoader = useSystemClassLoader == null ? Boolean.TRUE : useSystemClassLoader; + fork.setUseSystemClassLoader(useSystemClassLoader.booleanValue()); + fork.setUseManifestOnlyJar(useManifestOnlyJar); + + fork.setSystemProperties(systemProperties); + + if ("true".equals(debugForkedProcess)) { + debugForkedProcess = + "-Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005"; + } + + fork.setDebugLine(debugForkedProcess); + + if (jvm == null || "".equals(jvm)) { + // use the same JVM as the one used to run Maven (the "java.home" one) + jvm = System.getProperty("java.home") + File.separator + "bin" + File.separator + "java"; + getLog().debug("Using JVM: " + jvm); + } + + fork.setJvmExecutable(jvm); + + if (workingDirectory != null) { + fork.setWorkingDirectory(workingDirectory); + } else { + fork.setWorkingDirectory(basedir); + } + + fork.setArgLine(argLine); + + fork.setEnvironmentVariables(environmentVariables); + + if (getLog().isDebugEnabled()) { + showMap(environmentVariables, "environment variable"); + + fork.setDebug(true); + } + + if (argLine != null) { + List args = Arrays.asList(argLine.split(" ")); + if (args.contains("-da") || args.contains("-disableassertions")) { + enableAssertions = false; + } + } + } + + surefireBooter.setFailIfNoTests(failIfNoTests == null ? false : failIfNoTests.booleanValue()); + + surefireBooter.setForkedProcessTimeoutInSeconds(forkedProcessTimeoutInSeconds); + + surefireBooter.setRedirectTestOutputToFile(redirectTestOutputToFile); + + surefireBooter.setForkConfiguration(fork); + + surefireBooter.setChildDelegation(childDelegation); + + surefireBooter.setEnableAssertions(enableAssertions); + + surefireBooter.setReportsDirectory(reportsDirectory); + + addReporters(surefireBooter, fork.isForking()); + + return surefireBooter; + } + + private void showMap(Map map, String setting) { + for (Iterator i = map.keySet().iterator(); i.hasNext();) { + String key = (String)i.next(); + String value = (String)map.get(key); + getLog().debug("Setting " + setting + " [" + key + "]=[" + value + "]"); + } + } + + private void addProvider(OSGiSurefireBooter surefireBooter, + String provider, + String version, + Artifact filteredArtifact) throws ArtifactNotFoundException, ArtifactResolutionException { + Artifact providerArtifact = + artifactFactory.createDependencyArtifact("org.apache.maven.surefire", provider, VersionRange + .createFromVersion(version), "jar", null, Artifact.SCOPE_TEST); + ArtifactResolutionResult result = resolveArtifact(filteredArtifact, providerArtifact); + + for (Iterator i = result.getArtifacts().iterator(); i.hasNext();) { + Artifact artifact = (Artifact)i.next(); + + String key = ArtifactUtils.versionlessKey(artifact); + if("junit:junit".equals(key) || "jnuit:junit-dep".equals(key)) { + // Skip junit as it will be pulled from the test case dependencies + continue; + } + getLog().debug("Adding to surefire test classpath: " + artifact.getFile().getAbsolutePath()); + + surefireBooter.addSurefireClassPathUrl(artifact.getFile().getAbsolutePath()); + } + } + + private ArtifactResolutionResult resolveArtifact(Artifact filteredArtifact, Artifact providerArtifact) + throws ArtifactResolutionException, ArtifactNotFoundException { + ArtifactFilter filter = null; + if (filteredArtifact != null) { + filter = + new ExcludesArtifactFilter(Collections.singletonList(filteredArtifact.getGroupId() + ":" + + filteredArtifact.getArtifactId())); + } + + Artifact originatingArtifact = artifactFactory.createBuildArtifact("dummy", "dummy", "1.0", "jar"); + + return artifactResolver.resolveTransitively(Collections.singleton(providerArtifact), + originatingArtifact, + localRepository, + remoteRepositories, + metadataSource, + filter); + } + + private void addArtifact(OSGiSurefireBooter surefireBooter, Artifact surefireArtifact) + throws ArtifactNotFoundException, ArtifactResolutionException { + ArtifactResolutionResult result = resolveArtifact(null, surefireArtifact); + + for (Iterator i = result.getArtifacts().iterator(); i.hasNext();) { + Artifact artifact = (Artifact)i.next(); + + getLog().debug("Adding to surefire booter test classpath: " + artifact.getFile().getAbsolutePath()); + + surefireBooter.addSurefireBootClassPathUrl(artifact.getFile().getAbsolutePath()); + } + } + + protected void processSystemProperties(boolean setInSystem) { + if (systemProperties == null) { + systemProperties = new Properties(); + } + + originalSystemProperties = (Properties)System.getProperties().clone(); + + // We used to take all of our system properties and dump them in with the + // user specified properties for SUREFIRE-121, causing SUREFIRE-491. + // Not gonna do THAT any more... but I'm leaving this code here in case + // we need it later when we try to fix SUREFIRE-121 again. + + // Get the properties from the MavenSession instance to make embedded use work correctly + Properties userSpecifiedProperties = (Properties)session.getExecutionProperties().clone(); + userSpecifiedProperties.putAll(systemProperties); + //systemProperties = userSpecifiedProperties; + + systemProperties.setProperty("basedir", basedir.getAbsolutePath()); + systemProperties.setProperty("user.dir", workingDirectory.getAbsolutePath()); + + systemProperties.setProperty("localRepository", localRepository.getBasedir()); + + if (setInSystem) { + // Add all system properties configured by the user + Iterator iter = systemProperties.keySet().iterator(); + + while (iter.hasNext()) { + String key = (String)iter.next(); + + String value = systemProperties.getProperty(key); + + System.setProperty(key, value); + } + } + } + + /** + *

+ * Adds Reporters that will generate reports with different formatting. + *

+ * The Reporter that will be added will be based on the value of the parameter useFile, reportFormat, and + * printSummary. + * + * @param surefireBooter The surefire booter that will run tests. + * @param forking + */ + private void addReporters(OSGiSurefireBooter surefireBooter, boolean forking) { + Boolean trimStackTrace = Boolean.valueOf(this.trimStackTrace); + if (useFile) { + if (printSummary) { + if (forking) { + surefireBooter.addReport(ForkingConsoleReporter.class.getName(), new Object[] {trimStackTrace}); + } else { + surefireBooter.addReport(ConsoleReporter.class.getName(), new Object[] {trimStackTrace}); + } + } + + if (BRIEF_REPORT_FORMAT.equals(reportFormat)) { + surefireBooter.addReport(BriefFileReporter.class.getName(), new Object[] {reportsDirectory, + trimStackTrace}); + } else if (PLAIN_REPORT_FORMAT.equals(reportFormat)) { + surefireBooter.addReport(FileReporter.class.getName(), new Object[] {reportsDirectory, trimStackTrace}); + } + } else { + if (BRIEF_REPORT_FORMAT.equals(reportFormat)) { + surefireBooter.addReport(BriefConsoleReporter.class.getName(), new Object[] {trimStackTrace}); + } else if (PLAIN_REPORT_FORMAT.equals(reportFormat)) { + surefireBooter.addReport(DetailedConsoleReporter.class.getName(), new Object[] {trimStackTrace}); + } + } + + if (!disableXmlReport) { + surefireBooter.addReport(XMLReporter.class.getName(), new Object[] {reportsDirectory, trimStackTrace}); + } + } + + /** + * @return SurefirePlugin Returns the skipExec. + */ + public boolean isSkipExec() { + return this.skipTests; + } + + /** + * @param skipExec the skipExec to set + */ + public void setSkipExec(boolean skipExec) { + this.skipTests = skipExec; + } + + //TODO remove the part with ToolchainManager lookup once we depend on + //3.0.9 (have it as prerequisite). Define as regular component field then. + private Toolchain getToolchain() { + Toolchain tc = null; + try { + if (session != null) //session is null in tests.. + { + ToolchainManager toolchainManager = + (ToolchainManager)session.getContainer().lookup(ToolchainManager.ROLE); + if (toolchainManager != null) { + tc = toolchainManager.getToolchainFromBuildContext("jdk", session); + } + } + } catch (ComponentLookupException componentLookupException) { + //just ignore, could happen in pre-3.0.9 builds.. + } + return tc; + } + + protected Artifact getArtifact(String groupId, String artifactId) throws MojoExecutionException { + Artifact artifact; + VersionRange vr; + try { + vr = VersionRange.createFromVersionSpec(project.getVersion()); + } catch (InvalidVersionSpecificationException e1) { + vr = VersionRange.createFromVersion(project.getVersion()); + } + artifact = artifactFactory.createDependencyArtifact(groupId, artifactId, vr, "jar", null, Artifact.SCOPE_TEST); + + try { + artifactResolver.resolve(artifact, remoteRepositories, localRepository); + } catch (ArtifactResolutionException e) { + throw new MojoExecutionException("Unable to resolve artifact.", e); + } catch (ArtifactNotFoundException e) { + throw new MojoExecutionException("Unable to find artifact.", e); + } + + return artifact; + } + + private void generateJar(File root, File jar, Manifest mf) throws IOException { + getLog().info("Generating " + jar.toString()); + FileOutputStream fos = new FileOutputStream(jar); + JarOutputStream jos = mf != null ? new JarOutputStream(fos, mf) : new JarOutputStream(fos); + addDir(jos, root, root); + jos.close(); + } + + /** + * Convert the maven version into OSGi version + * @param mavenVersion + * @return + */ + static String osgiVersion(String mavenVersion) { + ArtifactVersion ver = new DefaultArtifactVersion(mavenVersion); + String qualifer = ver.getQualifier(); + if (qualifer != null) { + StringBuffer buf = new StringBuffer(qualifer); + for (int i = 0; i < buf.length(); i++) { + char c = buf.charAt(i); + if (Character.isLetterOrDigit(c) || c == '-' || c == '_') { + // Keep as-is + } else { + buf.setCharAt(i, '_'); + } + } + qualifer = buf.toString(); + } + Version osgiVersion = + new Version(ver.getMajorVersion(), ver.getMinorVersion(), ver.getIncrementalVersion(), qualifer); + String version = osgiVersion.toString(); + return version; + } + + private Manifest createMainBundle() throws IOException { + File mf = new File(project.getBasedir(), "META-INF/MANIFEST.MF"); + Manifest manifest = null; + if (mf.isFile()) { + manifest = new Manifest(new FileInputStream(mf)); + String bundleName = manifest.getMainAttributes().getValue(BUNDLE_SYMBOLICNAME); + if (bundleName != null) { + return manifest; + } + } + if (manifest == null) { + manifest = new Manifest(); + } + Attributes attributes = manifest.getMainAttributes(); + attributes.putValue("Manifest-Version", "1.0"); + attributes.putValue(BUNDLE_MANIFESTVERSION, "2"); + attributes.putValue(BUNDLE_SYMBOLICNAME, project.getGroupId() + "." + project.getArtifactId()); + attributes.putValue(BUNDLE_NAME, project.getName()); + attributes.putValue(BUNDLE_VERSION, osgiVersion(project.getVersion())); + attributes.putValue(Constants.DYNAMICIMPORT_PACKAGE, "*"); + return manifest; + } + + private Manifest createTestFragment(Manifest mf) { + // Create a manifest + Manifest manifest = new Manifest(); + Attributes attributes = manifest.getMainAttributes(); + attributes.putValue("Manifest-Version", "1.0"); + attributes.putValue(BUNDLE_MANIFESTVERSION, "2"); + String host = mf.getMainAttributes().getValue(BUNDLE_SYMBOLICNAME); + int sc = host.indexOf(';'); + if (sc != -1) { + host = host.substring(0, sc); + } + attributes.putValue(BUNDLE_SYMBOLICNAME, host + ".tests"); + attributes.putValue(BUNDLE_NAME, mf.getMainAttributes().getValue(BUNDLE_NAME) + " Tests"); + attributes.putValue(BUNDLE_VERSION, mf.getMainAttributes().getValue(BUNDLE_VERSION)); + attributes.putValue(Constants.FRAGMENT_HOST, host + ";bundle-version=\"" + + mf.getMainAttributes().getValue(BUNDLE_VERSION) + + "\""); + // The main bundle may not have the dependency on JUNIT + attributes.putValue(Constants.DYNAMICIMPORT_PACKAGE, "*"); + return manifest; + } + + private void addDir(JarOutputStream jos, File root, File dir) throws IOException, FileNotFoundException { + for (File file : dir.listFiles()) { + if (file.isDirectory()) { + addDir(jos, root, file); + } else if (file.isFile()) { + // getLog().info(file.toString()); + String uri = root.toURI().relativize(file.toURI()).toString(); + if ("META-INF/MANIFEST.MF".equals(uri)) { + continue; + } + ZipEntry entry = new ZipEntry(uri); + jos.putNextEntry(entry); + byte[] buf = new byte[4096]; + FileInputStream in = new FileInputStream(file); + for (;;) { + int len = in.read(buf); + if (len > 0) { + jos.write(buf, 0, len); + } else { + break; + } + } + in.close(); + jos.closeEntry(); + } + } + } +} -- cgit v1.2.3