diff --git a/.travis.yml b/.travis.yml index 93be8f9..aef1217 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: java jdk: -- openjdk7 +- openjdk8 script: - mvn test -D-org.login.url="https://na14.salesforce.com" -D-org.username=$USERNAME -D-org.password=$PASSWORD -D-org.client.id=$CLIENT_ID -D-org.client.secret=$CLIENT_SECRET -D-org.wide.code.coverage.threshold=40 -D-team.code.coverage.threshold=40 -D-regex.for.selecting.source.classes.for.code.coverage.computation=HelloWorld -D-regex.for.selecting.test.classes.to.execute=HelloWord* -D-manifest.files.with.test.class.names.to.execute=ManifestFile_For_Unit_Test_Classes.txt -D-manifest.files.with.source.class.names.for.code.coverage.computation=ManifestFile.txt -D-max.test.execution.time.threshold=10 diff --git a/README.md b/README.md index 1258332..e4501d2 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,8 @@ Optional Parameters: - -max.test.execution.time.threshold : Maximum execution time(in minutes) for a test before it gets aborted - -proxy.host : Proxy host for external access - -proxy.port : Proxy port for external access +- -ignore.code.coverage.threshold : Build does not fail if the code coverage thresholds are not met +- -ignore.test.failure : Build does not fail if there are tests failures - -help : Displays options available for running this application Note: You must provide either of the (-regex.for.selecting.source.classes.for.code.coverage.computation OR -manifest.files.with.source.class.names.for.code.coverage.computation) AND either of -(regex.for.selecting.test.classes.to.execute OR -manifest.files.with.test.class.names.to.execute) diff --git a/SFDC_CLA.pdf b/SFDC_CLA.pdf deleted file mode 100644 index 45fb9f8..0000000 Binary files a/SFDC_CLA.pdf and /dev/null differ diff --git a/pom.xml b/pom.xml index c354b0b..0fb553e 100644 --- a/pom.xml +++ b/pom.xml @@ -12,7 +12,7 @@ com.sforce.cd.ApexUnit ApexUnit-core - 2.3.6 + 4.0.0 ApexUnit Apex Unit v 2.0 with enhanced metrics and advanced features diff --git a/src/main/java/com/sforce/cd/apexUnit/ApexUnitRunner.java b/src/main/java/com/sforce/cd/apexUnit/ApexUnitRunner.java index fba195b..0646f4f 100644 --- a/src/main/java/com/sforce/cd/apexUnit/ApexUnitRunner.java +++ b/src/main/java/com/sforce/cd/apexUnit/ApexUnitRunner.java @@ -22,6 +22,8 @@ package com.sforce.cd.apexUnit; +import java.io.File; +import java.time.*; import java.util.Arrays; import java.util.List; @@ -101,21 +103,24 @@ public static void main(String[] args) { } Long end = System.currentTimeMillis(); LOG.debug("Total Time taken by ApexUnit tool in secs: " + (end - start) / 1000); + String reportDir = System.getProperty("user.dir") + System.getProperty("file.separator") + LocalDate.now() + "_Report"; + File dir = new File(reportDir); + dir.mkdirs(); if (apexReportBeans != null && apexReportBeans.length > 0) { LOG.info("Total test methods executed: " + apexReportBeans.length); - String reportFile = "ApexUnitReport.xml"; + String reportFile = reportDir + System.getProperty("file.separator") + "ApexUnitReport.xml"; ApexUnitTestReportGenerator.generateTestReport(apexReportBeans, reportFile); } else { ApexUnitUtils.shutDownWithErrMsg("Unable to generate test report. " + "Did not find any test results for the job id"); } + boolean teamCodeCoverageThresholdError = false; + boolean orgWideCodeCoverageThresholdError = false; if (!skipCodeCoverageComputation) { - ApexCodeCoverageReportGenerator.generateHTMLReport(apexClassCodeCoverageBeans); + ApexCodeCoverageReportGenerator.generateHTMLReport(apexClassCodeCoverageBeans, dir); // validating the code coverage metrics against the thresholds // provided by the user - boolean teamCodeCoverageThresholdError = false; - boolean orgWideCodeCoverageThresholdError = false; if (ApexUnitCodeCoverageResults.teamCodeCoverage < CommandLineArguments.getTeamCodeCoverageThreshold()) { if (ApexUnitCodeCoverageResults.teamCodeCoverage == -1) { LOG.warn("No source class names provided. Team Code coverage not computed "); @@ -123,19 +128,18 @@ public static void main(String[] args) { teamCodeCoverageThresholdError = true; } } - if (ApexUnitCodeCoverageResults.orgWideCodeCoverage < CommandLineArguments - .getOrgWideCodeCoverageThreshold()) { + if (ApexUnitCodeCoverageResults.orgWideCodeCoverage < CommandLineArguments.getOrgWideCodeCoverageThreshold()) { orgWideCodeCoverageThresholdError = true; } - if (teamCodeCoverageThresholdError) { + if (teamCodeCoverageThresholdError && !CommandLineArguments.getSkipCoverageEnforcement()) { runTimeExceptionMessage += "Failed to meet the Team code coverage threshold : " + CommandLineArguments.getTeamCodeCoverageThreshold() + " The team code coverage for the given classes is: " + ApexUnitCodeCoverageResults.teamCodeCoverage + "%\n" + "Calibrate your threshold values if you are happy with the current code coverage\n"; } - if (orgWideCodeCoverageThresholdError) { + if (orgWideCodeCoverageThresholdError && !CommandLineArguments.getSkipCoverageEnforcement()) { runTimeExceptionMessage += "Failed to meet the Org code coverage threshold : " + CommandLineArguments.getOrgWideCodeCoverageThreshold() + " The org code coverage for the org is: " + ApexUnitCodeCoverageResults.orgWideCodeCoverage @@ -144,7 +148,7 @@ public static void main(String[] args) { } // if there are test failures, concatenate error messages - if (TestStatusPollerAndResultHandler.testFailures) { + if (TestStatusPollerAndResultHandler.testFailures && !CommandLineArguments.getIgnoreTestFailure()) { runTimeExceptionMessage += "Test failures amongst the Apex tests executed. "; if (TestStatusPollerAndResultHandler.failedTestMethods != null && TestStatusPollerAndResultHandler.failedTestMethods.size() > 0) { @@ -158,7 +162,10 @@ public static void main(String[] args) { if (!runTimeExceptionMessage.equals("")) { ApexUnitUtils.shutDownWithErrMsg(runTimeExceptionMessage); } else { - LOG.info("Success!! No test failures and all code coverage thresholds are met!! Exiting ApexUnit.. Good bye.."); + LOG.info("Build successful!!" + +(TestStatusPollerAndResultHandler.testFailures ? " - But "+TestStatusPollerAndResultHandler.failedTestMethods.size()+" Test failure(s) amongst the Apex tests executed : "+TestStatusPollerAndResultHandler.failedTestMethods.toString():" - No test failures!!") + +(teamCodeCoverageThresholdError || orgWideCodeCoverageThresholdError ? (teamCodeCoverageThresholdError ? " - Failed to meet the Team code coverage threshold : "+ApexUnitCodeCoverageResults.teamCodeCoverage+"% < "+CommandLineArguments.getTeamCodeCoverageThreshold()+"%":"")+(orgWideCodeCoverageThresholdError ? " - Failed to meet the Org code coverage threshold : "+ApexUnitCodeCoverageResults.orgWideCodeCoverage+"% < "+CommandLineArguments.getOrgWideCodeCoverageThreshold()+"%":""):" - All code coverage thresholds are met!!") + +" - Exiting ApexUnit.. Good bye.."); } } diff --git a/src/main/java/com/sforce/cd/apexUnit/arguments/CommandLineArguments.java b/src/main/java/com/sforce/cd/apexUnit/arguments/CommandLineArguments.java index d16a0a4..f822769 100644 --- a/src/main/java/com/sforce/cd/apexUnit/arguments/CommandLineArguments.java +++ b/src/main/java/com/sforce/cd/apexUnit/arguments/CommandLineArguments.java @@ -35,7 +35,10 @@ public class CommandLineArguments { public static final String ORG_CLIENT_SECRET = "-org.client.secret"; public static final String PROXY_HOST = "-proxy.host"; public static final String PROXY_PORT = "-proxy.port"; + public static final String IGNORE_CODE_COVERAGE_THRESHOLD_ENFORCEMENT = "-ignore.code.coverage.threshold"; + public static final String IGNORE_TEST_FAILURE = "-ignore.test.failure"; public static final String TEST_RELOAD = "-test.reload"; + public static final String HELP = "-help"; @@ -75,6 +78,10 @@ public class CommandLineArguments { static private String proxyHost; @Parameter(names = PROXY_PORT, description = "Proxy port if required for access.", validateWith = PositiveIntegerValidator.class, required = false) static private Integer proxyPort; + @Parameter(names = IGNORE_CODE_COVERAGE_THRESHOLD_ENFORCEMENT, description = "Build does not fail if the code coverage thresholds are not met.", required = false) + static private boolean skipCoverageEnforcement = false; + @Parameter(names = IGNORE_TEST_FAILURE, description = "Build does not fail if there are tests failures.", required = false) + static private boolean ignoreTestFailure = false; @Parameter(names = HELP, help = true, description = "Displays options available for running this application") static private boolean help; @Parameter(names = TEST_RELOAD, description = "Want to reload test if same class changes submitted again.", arity=1) @@ -145,6 +152,14 @@ public static void setClientSecret(String clientSecret) { CommandLineArguments.clientSecret = clientSecret; } + public static boolean getSkipCoverageEnforcement() { + return skipCoverageEnforcement; + } + + public static boolean getIgnoreTestFailure() { + return ignoreTestFailure; + } + public static boolean isHelp() { return help; } diff --git a/src/main/java/com/sforce/cd/apexUnit/client/QueryConstructor.java b/src/main/java/com/sforce/cd/apexUnit/client/QueryConstructor.java index fc0240b..397ab1a 100644 --- a/src/main/java/com/sforce/cd/apexUnit/client/QueryConstructor.java +++ b/src/main/java/com/sforce/cd/apexUnit/client/QueryConstructor.java @@ -292,7 +292,7 @@ private static String processRegexForSoqlQueries(String apexClassNameRegex) { } /* - * Escape single quotes in the user input during qyery execution + * Escape single quotes in the user input during query execution * * @param : userInput: String * diff --git a/src/main/java/com/sforce/cd/apexUnit/client/codeCoverage/CodeCoverageComputer.java b/src/main/java/com/sforce/cd/apexUnit/client/codeCoverage/CodeCoverageComputer.java old mode 100644 new mode 100755 index db3e5c6..e8d3edc --- a/src/main/java/com/sforce/cd/apexUnit/client/codeCoverage/CodeCoverageComputer.java +++ b/src/main/java/com/sforce/cd/apexUnit/client/codeCoverage/CodeCoverageComputer.java @@ -1,7 +1,7 @@ /* * Copyright (c) 2016, salesforce.com, inc. * All rights reserved. - *Licensed under the BSD 3-Clause license. + * Licensed under the BSD 3-Clause license. * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ @@ -9,7 +9,8 @@ * Class to compute code coverage for the given class names and org wide code coverage * * @author adarsh.ramakrishna@salesforce.com - */ + */ + package com.sforce.cd.apexUnit.client.codeCoverage; @@ -92,13 +93,13 @@ public ApexClassCodeCoverageBean[] calculateAggregatedCodeCoverageUsingToolingAP if (CommandLineArguments.getClassManifestFiles() != null) { LOG.debug(" Fetching apex classes from location : " + CommandLineArguments.getClassManifestFiles()); classesAsArray = ApexClassFetcherUtils - .fetchApexClassesFromManifestFiles(CommandLineArguments.getClassManifestFiles(),true); + .fetchApexClassesFromManifestFiles(CommandLineArguments.getClassManifestFiles(), true); } // fetch matching class names based on regex if (CommandLineArguments.getSourceRegex() != null) { LOG.debug(" Fetching apex classes with regex : " + CommandLineArguments.getSourceRegex()); classesAsArray = ApexClassFetcherUtils.fetchApexClassesBasedOnMultipleRegexes(connection, classesAsArray, - CommandLineArguments.getSourceRegex(),true); + CommandLineArguments.getSourceRegex(), true); } // Do not proceed if no class names are returned from both manifest // files and/or regexes @@ -146,6 +147,7 @@ public ApexClassCodeCoverageBean[] calculateAggregatedCodeCoverageUsingToolingAP } + // results are processed separately from thread submissions for (int i = 0; i < numOfBatches; i++) { try { recordObject.addAll((JSONArray) pool.take().get().get("records")); @@ -176,6 +178,8 @@ public ApexClassCodeCoverageBean[] calculateAggregatedCodeCoverageUsingToolingAP LOG.debug("responseJsonObject says " + responseJsonObject + "\n relativeServiceURL is " + relativeServiceURL + "\n soqlcc is " + soqlcc); if (responseJsonObject != null) { + String responseStr = responseJsonObject.toJSONString(); + LOG.debug(responseStr); apexClassCodeCoverageBeans = processJSONResponseAndConstructCodeCoverageBeans(connection, (JSONArray) responseJsonObject.get("records")); } @@ -392,4 +396,4 @@ public int getOrgWideCodeCoverage() { ApexUnitCodeCoverageResults.orgWideCodeCoverage = coverage; return coverage; } -} \ No newline at end of file +} diff --git a/src/main/java/com/sforce/cd/apexUnit/client/codeCoverage/WebServiceInvoker.java b/src/main/java/com/sforce/cd/apexUnit/client/codeCoverage/WebServiceInvoker.java old mode 100644 new mode 100755 index 8739aac..3d40b77 --- a/src/main/java/com/sforce/cd/apexUnit/client/codeCoverage/WebServiceInvoker.java +++ b/src/main/java/com/sforce/cd/apexUnit/client/codeCoverage/WebServiceInvoker.java @@ -25,12 +25,15 @@ import java.util.Set; import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.HostConfiguration; import org.apache.commons.httpclient.HttpException; import org.apache.commons.httpclient.URI; import org.apache.commons.httpclient.URIException; +import org.apache.commons.httpclient.cookie.CookiePolicy; import org.apache.commons.httpclient.methods.GetMethod; import org.apache.commons.httpclient.methods.PostMethod; import org.apache.commons.httpclient.methods.StringRequestEntity; +import org.apache.commons.httpclient.params.HttpClientParams; import org.json.simple.JSONObject; import org.json.simple.JSONValue; import org.slf4j.Logger; @@ -64,12 +67,25 @@ public HashMap doPost(String relativeServiceURL) { HttpClient httpclient = new HttpClient(); String requestString = ""; HashMap responseMap = new HashMap(); + try { // the client id and secret is applicable across all dev orgs requestString = generateRequestString(); String authorizationServerURL = CommandLineArguments.getOrgUrl() + relativeServiceURL; httpclient.getParams().setSoTimeout(0); + //solve Cookie rejected: "$Version=0; BrowserId=Su_SOgNxEeqrrjGlvnCIuA; $Path=/; $Domain=.salesforce.com". Domain attribute ".salesforce.com" violates RFC 2109: host minus domain may not contain any dots + httpclient.getParams().setParameter(HttpClientParams.COOKIE_POLICY, CookiePolicy.BROWSER_COMPATIBILITY); + + // Set proxy if needed + if (CommandLineArguments.getProxyHost() != null && CommandLineArguments.getProxyPort() != null) { + LOG.debug("Setting proxy configuraiton to " + CommandLineArguments.getProxyHost() + " on port " + + CommandLineArguments.getProxyPort()); + HostConfiguration hostConfiguration = httpclient.getHostConfiguration(); + hostConfiguration.setProxy(CommandLineArguments.getProxyHost(),CommandLineArguments.getProxyPort()); + httpclient.setHostConfiguration(hostConfiguration); + } + post = new PostMethod(authorizationServerURL); post.addRequestHeader("Content-Type", "application/x-www-form-urlencoded"); post.addRequestHeader("X-PrettyPrint", "1"); @@ -140,6 +156,16 @@ public static JSONObject doGet(String relativeServiceURL, String accessToken) { LOG.debug("relativeServiceURL in doGet method:" + relativeServiceURL); HttpClient httpclient = new HttpClient(); + //solve Cookie rejected: "$Version=0; BrowserId=Su_SOgNxEeqrrjGlvnCIuA; $Path=/; $Domain=.salesforce.com". Domain attribute ".salesforce.com" violates RFC 2109: host minus domain may not contain any dots + httpclient.getParams().setParameter(HttpClientParams.COOKIE_POLICY, CookiePolicy.BROWSER_COMPATIBILITY); + // Set proxy if needed + if (CommandLineArguments.getProxyHost() != null && CommandLineArguments.getProxyPort() != null) { + LOG.debug("Setting proxy configuraiton to " + CommandLineArguments.getProxyHost() + " on port " + + CommandLineArguments.getProxyPort()); + HostConfiguration hostConfiguration = httpclient.getHostConfiguration(); + hostConfiguration.setProxy(CommandLineArguments.getProxyHost(),CommandLineArguments.getProxyPort()); + httpclient.setHostConfiguration(hostConfiguration); + } GetMethod get = null; String authorizationServerURL = CommandLineArguments.getOrgUrl() + relativeServiceURL; diff --git a/src/main/java/com/sforce/cd/apexUnit/client/testEngine/TestExecutor.java b/src/main/java/com/sforce/cd/apexUnit/client/testEngine/TestExecutor.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/sforce/cd/apexUnit/client/testEngine/TestStatusPollerAndResultHandler.java b/src/main/java/com/sforce/cd/apexUnit/client/testEngine/TestStatusPollerAndResultHandler.java index bb259ac..c4df6a8 100644 --- a/src/main/java/com/sforce/cd/apexUnit/client/testEngine/TestStatusPollerAndResultHandler.java +++ b/src/main/java/com/sforce/cd/apexUnit/client/testEngine/TestStatusPollerAndResultHandler.java @@ -67,7 +67,7 @@ public ApexReportBean[] fetchResultsFromParentJobId(String parentJobId, PartnerC int index = 0; SObject[] sObjects = queryResult.getRecords(); if (sObjects != null) { - totalTestMethodsExecuted = sObjects.length; + totalTestMethodsExecuted += sObjects.length; LOG.info("Total test methods executed: " + TestStatusPollerAndResultHandler.totalTestMethodsExecuted); apexReportBeans = new ApexReportBean[sObjects.length]; for (SObject sobject : sObjects) { @@ -141,7 +141,7 @@ public boolean waitForTestsToComplete(String parentJobId, PartnerConnection conn if (sObjects != null) { String status = ""; int totalTests = sObjects.length; - totalTestClasses = totalTests; + totalTestClasses += totalTests; int remainingTests = totalTests; LOG.info("Total test classes to execute: " + totalTestClasses); String testId = ""; diff --git a/src/main/java/com/sforce/cd/apexUnit/report/ApexCodeCoverageReportGenerator.java b/src/main/java/com/sforce/cd/apexUnit/report/ApexCodeCoverageReportGenerator.java index 8d77dc5..700ba31 100644 --- a/src/main/java/com/sforce/cd/apexUnit/report/ApexCodeCoverageReportGenerator.java +++ b/src/main/java/com/sforce/cd/apexUnit/report/ApexCodeCoverageReportGenerator.java @@ -29,7 +29,7 @@ public class ApexCodeCoverageReportGenerator { - public static void generateHTMLReport(ApexClassCodeCoverageBean[] apexClassCodeCoverageBeans) { + public static void generateHTMLReport(ApexClassCodeCoverageBean[] apexClassCodeCoverageBeans, File reportDir) { // Preparing the table: StringBuilder htmlBuilder = new StringBuilder(); htmlBuilder.append(""); @@ -112,17 +112,17 @@ public static void generateHTMLReport(ApexClassCodeCoverageBean[] apexClassCodeC // provide link to the test report appendTag(htmlBuilder, "header", "Apex Test Report: "); appendLineSpaces(htmlBuilder, 2); - String workingDir = System.getProperty("user.dir"); + String workingDir = reportDir.getPath(); String apexUnitTestReportPath = ""; if (!workingDir.contains("jenkins")) { - apexUnitTestReportPath = workingDir + System.getProperty("file.separator") + "ApexUnitReport.xml"; + apexUnitTestReportPath = "." + System.getProperty("file.separator") + "ApexUnitReport.xml"; } else { int lastIndexOfSlash = workingDir.lastIndexOf('/'); String jobName = workingDir.substring(lastIndexOfSlash + 1); apexUnitTestReportPath = "https://jenkins.internal.salesforce.com/job/" + jobName + "/lastCompletedBuild/testReport/"; } - appendTag(htmlBuilder, "a", "style=\"font-size:125%\"; href=" + apexUnitTestReportPath, "Detailed Test Report"); + appendTag(htmlBuilder, "a", "style=\"font-size:125%\"; href=\"" + apexUnitTestReportPath + "\"", "Detailed Test Report"); appendLineSpaces(htmlBuilder, 2); appendTag(htmlBuilder, "header", "Detailed code coverage report: "); @@ -216,17 +216,14 @@ public static void generateHTMLReport(ApexClassCodeCoverageBean[] apexClassCodeC htmlBuilder.append(""); htmlBuilder.append(""); - createHTMLReport(htmlBuilder.toString()); + createHTMLReport(htmlBuilder.toString(), reportDir); } - private static void createHTMLReport(String htmlBuffer) { + private static void createHTMLReport(String htmlBuffer, File reportDir) { File tmpFile = null; FileOutputStream tmpOut = null; - String workingDir = System.getProperty("user.dir") + System.getProperty("file.separator") + "Report"; - File dir = new File(workingDir); - dir.mkdirs(); - tmpFile = new File(dir, "ApexUnitReport.html"); + tmpFile = new File(reportDir, "ApexUnitReport.html"); byte[] reportAsBytes; try { tmpOut = new FileOutputStream(tmpFile);