com.salesforce.ide.ui.views.runtest.RunTestsView.java Source code

Java tutorial

Introduction

Here is the source code for com.salesforce.ide.ui.views.runtest.RunTestsView.java

Source

/*******************************************************************************
 * Copyright (c) 2015 Salesforce.com, inc..
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors:
 *     Salesforce.com, inc. - initial API and implementation
 ******************************************************************************/

package com.salesforce.ide.ui.views.runtest;

import java.io.IOException;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;

import org.apache.log4j.Logger;
import org.eclipse.core.filebuffers.FileBuffers;
import org.eclipse.core.filebuffers.ITextFileBuffer;
import org.eclipse.core.filebuffers.ITextFileBufferManager;
import org.eclipse.core.filebuffers.LocationKind;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jface.resource.FontRegistry;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.ui.ISelectionListener;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.ide.IDE;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Predicate;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.salesforce.ide.apex.internal.core.ApexSourceUtils;
import com.salesforce.ide.core.internal.context.ContainerDelegate;
import com.salesforce.ide.core.internal.utils.DialogUtils;
import com.salesforce.ide.core.internal.utils.QualifiedNames;
import com.salesforce.ide.core.internal.utils.ResourceProperties;
import com.salesforce.ide.core.internal.utils.Utils;
import com.salesforce.ide.core.model.ApexCodeLocation;
import com.salesforce.ide.core.project.ForceProject;
import com.salesforce.ide.core.project.MarkerUtils;
import com.salesforce.ide.core.remote.ForceConnectionException;
import com.salesforce.ide.core.remote.ForceRemoteException;
import com.salesforce.ide.core.remote.HTTPAdapter;
import com.salesforce.ide.core.remote.HTTPAdapter.HTTPMethod;
import com.salesforce.ide.core.remote.HTTPConnection;
import com.salesforce.ide.core.remote.ToolingStubExt;
import com.salesforce.ide.core.remote.tooling.ApexCodeCoverageAggregate.*;
import com.salesforce.ide.core.remote.tooling.ApexLog.*;
import com.salesforce.ide.core.remote.tooling.Limits.*;
import com.salesforce.ide.core.remote.tooling.RunTests.*;
import com.salesforce.ide.core.remote.tooling.ToolingQueryCommand;
import com.salesforce.ide.core.remote.tooling.ToolingQueryTransport;
import com.salesforce.ide.core.remote.tooling.TraceFlagUtil;
import com.salesforce.ide.ui.internal.utils.UIConstants;
import com.salesforce.ide.ui.internal.utils.UIUtils;
import com.salesforce.ide.ui.views.BaseViewPart;
import com.sforce.soap.tooling.sobject.AggregateResult;
import com.sforce.soap.tooling.sobject.ApexLog;
import com.sforce.soap.tooling.ApexLogLevel;
import com.sforce.soap.tooling.sobject.ApexOrgWideCoverage;
import com.sforce.soap.tooling.ApexTestOutcome;
import com.sforce.soap.tooling.sobject.ApexTestQueueItem;
import com.sforce.soap.tooling.sobject.ApexTestResult;
import com.sforce.soap.tooling.AsyncApexJobStatus;
import com.sforce.soap.tooling.LogCategory;
import com.sforce.soap.tooling.QueryResult;
import com.sforce.soap.tooling.sobject.SObject;

/**
 * Responsible for running tests, getting results, and updating the UI with the test results.
 * The actual view is generated by RunTestsViewComposite.java.
 * 
 * @author jwidjaja
 *
 */
public class RunTestsView extends BaseViewPart {
    private static final class ApexTestResultComparator implements Comparator<ApexTestResult> {
        @Override
        public int compare(ApexTestResult tr1, ApexTestResult tr2) {
            int compareDir = tr1.getApexClass().getName().compareTo(tr2.getApexClass().getName());
            if (compareDir == 0) {
                return tr1.getMethodName().compareTo(tr2.getMethodName());
            }

            return compareDir;
        }
    }

    private static final Logger logger = Logger.getLogger(RunTestsView.class);

    private static RunTestsView INSTANCE = null;

    @VisibleForTesting
    public ReentrantLock lock = new ReentrantLock();
    @VisibleForTesting
    public ForceProject forceProject = null;
    @VisibleForTesting
    public ToolingStubExt toolingStubExt = null;

    private RunTestsViewComposite runTestComposite = null;
    private IProject project = null;
    private HTTPConnection toolingRESTConnection = null;
    private ISelectionListener fPostSelectionListener = null;
    private TraceFlagUtil tfUtil = null;
    private boolean shouldCreateTraceFlag = false;

    public RunTestsView() {
        super();
        setSelectionListener();

        INSTANCE = this;
    }

    public static RunTestsView getInstance() {
        if (Utils.isEmpty(INSTANCE)) {
            // We use Display.syncExec because getting a view has to be done
            // on a UI thread.
            Display display = PlatformUI.getWorkbench().getDisplay();
            if (Utils.isEmpty(display))
                return INSTANCE;

            display.syncExec(new Runnable() {
                @Override
                public void run() {
                    try {
                        INSTANCE = (RunTestsView) PlatformUI.getWorkbench().getActiveWorkbenchWindow()
                                .getActivePage().showView(UIConstants.RUN_TEST_VIEW_ID);
                    } catch (Exception e) {
                        logger.error("Failed to get Apex Test Results view", e);
                    }
                }
            });
        }

        return INSTANCE;
    }

    /**
     * Is there a test run in progress? If yes, this method
     * returns false. If no, this method returns true.
     */
    public boolean canRun() {
        return (lock != null && !lock.isLocked());
    }

    /**
     * Check if there is an existing active Trace Flag.
     */
    public boolean hasExistingTraceFlag(IProject project) {
        ForceProject fp = materializeForceProject(project);
        TraceFlagUtil tf = getTraceFlagUtil(fp);
        return tf.hasActiveTraceFlag();
    }

    /**
     * Show error message to user.
     * TODO: Just throw the exception? Double check all logger.error
     */
    private void throwErrorMsg(final String title, final String solution) {
        if (Utils.isNotEmpty(title) && Utils.isNotEmpty(solution)) {
            Display display = PlatformUI.getWorkbench().getDisplay();
            display.asyncExec(new Runnable() {
                @Override
                public void run() {
                    DialogUtils.getInstance().okMessage(title, solution);
                }
            });

        }
    }

    /**
     * Run the tests, get the results, and update the UI.
     */
    @VisibleForTesting
    public void runTests(final IProject project, String testsInJson, boolean shouldUseSuites, boolean isAsync,
            boolean isDebugging, boolean hasExistingTraceFlag, boolean shouldCreateTraceFlag,
            Map<LogCategory, ApexLogLevel> logLevels, IProgressMonitor monitor) {

        forceProject = materializeForceProject(project);
        if (Utils.isEmpty(forceProject)) {
            logger.error("Unable to find Force.com project");
            return;
        }

        lock.lock();

        tfUtil = getTraceFlagUtil(forceProject);
        String debugLevelId = null, traceFlagId = null;

        try {
            prepareForRunningTests(project);
            this.shouldCreateTraceFlag = shouldCreateTraceFlag;

            // Set user TraceFlag if launch config enabled logging and there isn't
            // an existing TraceFlag
            if (this.shouldCreateTraceFlag && !hasExistingTraceFlag) {
                String userName = forceProject.getUserName();
                String userId = tfUtil.getUserId(userName);
                debugLevelId = tfUtil.insertDebugLevel(
                        RunTestsConstants.DEBUG_LEVEL_NAME + System.currentTimeMillis(), logLevels);
                traceFlagId = tfUtil.insertTraceFlag(userId, RunTestsConstants.TF_LENGTH_MINS, debugLevelId);
                tfUtil.automateTraceFlagExtension(traceFlagId, RunTestsConstants.TF_INTERVAL_MINS,
                        RunTestsConstants.TF_LENGTH_MINS);
            }

            if (!monitor.isCanceled()) {
                // Submit the tests for execution
                String enqueueResult = enqueueTests(testsInJson, shouldUseSuites, isAsync, isDebugging);

                Map<IResource, List<String>> testResources = findTestClasses(project);

                if (Utils.isNotEmpty(enqueueResult) && isAsync) {
                    // If it's an async run, the enqueue result is an async test run ID, so we poll for test results
                    getAsyncTestResults(enqueueResult, testResources, monitor);
                    // Display code coverage from ApexCodeCoverageAggregate & ApexOrgWideCoverage
                    displayCodeCoverage();
                } else if (Utils.isNotEmpty(enqueueResult) && !isAsync) {
                    /*
                     * Sync test runs do create ApexTestResult objects, but there's no way to query the
                     * right ones for this test run because they don't have a sync test run ID, only async
                     * test run ID. So, we have to rely on the response of runTestsSynchronous.
                     */
                    ObjectMapper mapper = new ObjectMapper();
                    RunTestsSyncResponse testResults;
                    try {
                        testResults = mapper.readValue(enqueueResult, RunTestsSyncResponse.class);
                        // Test results are returned all at once
                        updateProgress(0, testResults.getNumTestsRun(), testResults.getNumTestsRun());
                        // Tests were submitted in alphabetical order. We're relying on the
                        // server to return test results in the same order to avoid sorting
                        // the results
                        processSyncTestResults(project, testResources, testResults);
                        // Display code coverage from ApexCodeCoverageAggregate & ApexOrgWideCoverage
                        displayCodeCoverage();
                    } catch (IOException e) {
                        logger.error(String.format("Problem reading test result: %s", enqueueResult));
                    }
                }
            }
        } catch (Exception e) {
            logger.error("Unexpected error", e);
        } finally {
            finishRunningTests();

            if (lock.isLocked()) {
                lock.unlock();
            }

            // Clean up TraceFlag if it was created
            if (this.shouldCreateTraceFlag && !hasExistingTraceFlag) {
                tfUtil.removeTraceFlagJobs();
                tfUtil.deleteTraceflagAndDebugLevel(traceFlagId, debugLevelId);
            }
        }
    }

    /**
     * Clear the test results view and disable Clear button.
     */
    @VisibleForTesting
    public void prepareForRunningTests(final IProject project) {
        Display display = PlatformUI.getWorkbench().getDisplay();
        display.syncExec(new Runnable() {
            @Override
            public void run() {
                setProject(project);
                runTestComposite.clearAll();
                runTestComposite.setClearButton(false);
            }
        });
    }

    /**
     * Enable Clear button.
     */
    public void finishRunningTests() {
        Display display = PlatformUI.getWorkbench().getDisplay();
        display.syncExec(new Runnable() {
            @Override
            public void run() {
                runTestComposite.setClearButton(true);
            }
        });
    }

    /**
    * Get a ForceProject from an IProject.
    */
    @VisibleForTesting
    public ForceProject materializeForceProject(IProject project) {
        if (Utils.isEmpty(project) || !project.exists())
            return null;

        ForceProject forceProject = ContainerDelegate.getInstance().getServiceLocator().getProjectService()
                .getForceProject(project);
        return forceProject;
    }

    /**
     * Create a new TraceFlagUtil.
     */
    @VisibleForTesting
    public TraceFlagUtil getTraceFlagUtil(ForceProject forceProject) {
        return new TraceFlagUtil(forceProject);
    }

    /**
     * Enqueue a tests array to Tooling's runTestsAsynchronous/runTestsSynchronous.
     * @return The test run ID if valid async run. 
     * The test results if valid sync run. Null otherwise.
     */
    @VisibleForTesting
    public String enqueueTests(String testsInJson, boolean shouldUseSuites, boolean isAsync, boolean isDebugging) {
        String response = null;

        if (Utils.isEmpty(forceProject)) {
            return null;
        }

        try {
            int timeoutVal = getConnTimeoutVal(isAsync, isDebugging);
            initializeConnection(forceProject, timeoutVal);

            RunTestsCommand job = getRunTestsCommand(testsInJson, isAsync);
            job.schedule();

            job.join();
            response = job.getAnswer();
            if (job.wasError()) {
                String enqueueErr = job.getErrorMsg();
                if (shouldUseSuites && Utils.isNotEmpty(enqueueErr)
                        && enqueueErr.contains("Please enter valid suiteids")) {
                    enqueueErr = Messages.View_ErrorInvalidSuites;
                }

                logger.error(String.format("Failed to enqueue tests. Tests array: %s", testsInJson));
                throwErrorMsg(Messages.View_ErrorStartingTestsTitle, enqueueErr);
            }
        } catch (Exception e) {
            logger.error(String.format("Tests array: %s. Error message: %s", testsInJson, e.getMessage()));
            throwErrorMsg(Messages.View_ErrorStartingTestsTitle, e.getMessage());
        }

        return response;
    }

    /**
     * Get connection timeout value depending on
     * test mode and debugging status.
     * @return Timeout value
     */
    @VisibleForTesting
    public int getConnTimeoutVal(boolean isAsync, boolean isDebugging) {
        return (isAsync ? RunTestsConstants.ASYNC_TIMEOUT
                : (isDebugging ? RunTestsConstants.SYNC_WITH_DEBUG_TIMEOUT
                        : RunTestsConstants.SYNC_WITHOUT_DEBUG_TIMEOUT));
    }

    /**
     * Create the job to submit to Tooling API's run tests endpoint.
     * @return Promiseable job
     */
    @VisibleForTesting
    public RunTestsCommand getRunTestsCommand(String testsInJson, boolean isAsync) {
        return new RunTestsCommand(new HTTPAdapter<>(String.class,
                new RunTestsTransport(toolingRESTConnection, isAsync), HTTPMethod.POST), testsInJson);
    }

    /**
     * Retrieve test results for the given test run ID.
     * @return List of ApexTestResult
     */
    @VisibleForTesting
    public void getAsyncTestResults(String testRunId, final Map<IResource, List<String>> testResources,
            IProgressMonitor monitor) {
        if (Utils.isEmpty(forceProject) || Utils.isEmpty(testRunId))
            return;
        testRunId = testRunId.replace("\"", "");

        try {
            initializeConnection(forceProject);

            // Get remaining daily API requests
            Limit dailyApiRequests = getApiLimit(forceProject, LimitsCommand.Type.DailyApiRequests);
            if (dailyApiRequests == null) {
                logger.error("Failed to get DailyApiRequests limit");
                return;
            }

            float apiRequestsRemaining = (dailyApiRequests.getRemaining() * 100.0f) / dailyApiRequests.getMax();
            if (apiRequestsRemaining <= 0) {
                logger.error("Not enough DailyApiRequests");
                return;
            }

            // Poll for remaining test cases to be executed
            // No timeout here because we don't know how long a test run can be.
            // If user wants to exit, then they can cancel the launch config.
            Integer totalItems = queryTotalQueueItems(testRunId);

            int processedItems = 0;
            while (processedItems < totalItems) {
                processedItems = queryProcessedQueueItem(testRunId);
                List<ApexTestResult> testResults = queryTestResults(testRunId);

                // Update progress bar and results view if new results came in
                updateProgress(0, totalItems, processedItems);

                Collections.sort(testResults, new ApexTestResultComparator());
                processAsyncTestResults(testResources, testResults, processedItems == totalItems);

                // User wants to abort so we'll tell the server to abort the test run
                // and stop polling for test results. There may be some finished test results
                // so try to query those and update UI if necessary.
                if (monitor.isCanceled()) {
                    abortTestRun(testRunId);
                    break;
                }

                // Wait according to the interval
                int wait = getPollInterval(totalItems - processedItems, apiRequestsRemaining);
                Thread.sleep(wait);
            }
        } catch (Exception e) {
            logger.error("Failed to get test results", e);
            throwErrorMsg(Messages.View_ErrorGetAsyncTestResultsTitle,
                    Messages.View_ErrorGetAsyncTestResultsSolution);
        }
    }

    private List<ApexTestResult> queryTestResults(String testRunId) throws ForceRemoteException {
        List<ApexTestResult> testResults = Lists.newLinkedList();
        // Query for test results (one ApexTestResult object per test method)
        QueryResult qr = toolingStubExt.query(String.format(RunTestsConstants.QUERY_TESTRESULT, testRunId));
        if (qr != null && qr.getSize() > 0) {
            // Even though the query specifies the sort order, getRecords() messages up the order.
            // Need to sort before displaying the results
            for (SObject sObj : qr.getRecords()) {
                ApexTestResult testResult = (ApexTestResult) sObj;
                testResults.add(testResult);
            }
        }
        return testResults;
    }

    // Number of items that have been processed (i.,e anything no longer in queue so success, failure, etc).
    @VisibleForTesting
    public int queryProcessedQueueItem(String testRunId) throws ForceRemoteException {
        int processedItems;
        QueryResult processedQuery = toolingStubExt
                .query(String.format(RunTestsConstants.QUERY_APEX_TEST_QUEUE_ITEM_PROCESSED_COUNT, testRunId));
        AggregateResult processedAgg = (AggregateResult) processedQuery.getRecords()[0];
        processedItems = (Integer) processedAgg.getField("total");
        return processedItems;
    }

    // Number of items that have been designated for processing.
    @VisibleForTesting
    public Integer queryTotalQueueItems(String testRunId) throws ForceRemoteException {
        QueryResult totalQuery = toolingStubExt
                .query(String.format(RunTestsConstants.QUERY_APEX_TEST_QUEUE_ITEM_COUNT, testRunId));
        AggregateResult totalAgg = (AggregateResult) totalQuery.getRecords()[0];
        Integer totalItems = (Integer) totalAgg.getField("total");
        return totalItems;
    }

    /**
     * Get a specific API Limit
     * @see Limit.java
     */
    @VisibleForTesting
    public Limit getApiLimit(ForceProject forceProject, LimitsCommand.Type type) {
        try {
            initializeConnection(forceProject);

            LimitsCommand job = new LimitsCommand(
                    new HTTPAdapter<>(String.class, new LimitsTransport(toolingRESTConnection), HTTPMethod.GET));
            job.schedule();

            try {
                job.join();
                String limitsResponse = job.getAnswer();

                if (job.wasError()) {
                    logger.error(String.format("Failed to get API limits. Error message: %s", job.getErrorMsg()));
                }

                Map<String, Limit> limits = job.parseLimits(limitsResponse);
                if (limits != null && limits.size() > 0) {
                    return limits.get(type.toString());
                }
            } catch (InterruptedException e) {
                logger.error("Failed to get API limits", e);
            }
        } catch (ForceConnectionException | ForceRemoteException e) {
            logger.error("Failed to connect to Tooling API", e);
        }

        return null;
    }

    /**
     * Get the appropriate poll interval depending on the number of ApexTestQueueItems remaining
     * and the number of API requests remaining. The higher the number of tests remaining, the slower
     * we should poll. The higher the number of remaining API requests, the faster we should poll.
     * @return A poll interval
     */
    @VisibleForTesting
    public int getPollInterval(int queuedItemsRemaining, float apiRequestsRemaining) {
        int intervalA = RunTestsConstants.POLL_SLOW, intervalB = RunTestsConstants.POLL_SLOW;

        if (queuedItemsRemaining <= 2) {
            intervalA = RunTestsConstants.POLL_FAST;
        } else if (queuedItemsRemaining <= 10) {
            intervalA = RunTestsConstants.POLL_MED;
        } else {
            intervalA = RunTestsConstants.POLL_SLOW;
        }

        if (apiRequestsRemaining <= 25f) {
            intervalB = RunTestsConstants.POLL_SLOW;
        } else if (apiRequestsRemaining <= 50f) {
            intervalB = RunTestsConstants.POLL_MED;
        } else {
            intervalB = RunTestsConstants.POLL_FAST;
        }

        return (intervalA + intervalB) / 2;
    }

    /**
     * Update the progress bar to show user the number of tests finished.
     */
    @VisibleForTesting
    public void updateProgress(final int min, final int max, final int cur) {
        Display display = PlatformUI.getWorkbench().getDisplay();
        display.syncExec(new Runnable() {
            @Override
            public void run() {
                if (Utils.isNotEmpty(runTestComposite)) {
                    runTestComposite.setProgress(min, max, cur);
                }
            }
        });
    }

    /**
     * Abort all ApexTestQueueItem with the same test run ID.
     */
    @VisibleForTesting
    public boolean abortTestRun(String testRunId) {
        if (Utils.isEmpty(forceProject) || Utils.isEmpty(testRunId)) {
            return false;
        }

        try {
            initializeConnection(forceProject);

            // Get all ApexTestQueueItem in the test run
            QueryResult qr = toolingStubExt
                    .query(String.format(RunTestsConstants.QUERY_APEX_TEST_QUEUE_ITEM, testRunId));
            if (Utils.isEmpty(qr) || qr.getSize() == 0)
                return false;

            // Update status to Aborted
            List<ApexTestQueueItem> abortedList = Lists.newArrayList();
            for (SObject sObj : qr.getRecords()) {
                ApexTestQueueItem atqi = (ApexTestQueueItem) sObj;
                // If the queue item is not done yet, abort them
                if (!atqi.getStatus().equals(AsyncApexJobStatus.Completed)
                        && !atqi.getStatus().equals(AsyncApexJobStatus.Failed)) {
                    atqi.setStatus(AsyncApexJobStatus.Aborted);
                    abortedList.add(atqi);
                }
            }

            // Update in chunks because there is a limit to how many we can update in one call
            if (!abortedList.isEmpty()) {
                for (List<ApexTestQueueItem> subList : Lists.partition(abortedList, 200)) {
                    ApexTestQueueItem[] abortedArray = subList.toArray(new ApexTestQueueItem[subList.size()]);
                    toolingStubExt.update(abortedArray);
                }
                return true;
            }
        } catch (ForceConnectionException | ForceRemoteException e) {
            logger.error("Failed to abort test run", e);
        }

        return false;
    }

    /**
     * Update the UI with the test results for an asynchronous test run.
     */
    @VisibleForTesting
    public void processAsyncTestResults(final Map<IResource, List<String>> testResources,
            final List<ApexTestResult> testResults, final boolean expandFailedResults) {
        if (Utils.isEmpty(testResources) || Utils.isEmpty(testResults)) {
            return;
        }

        Display display = PlatformUI.getWorkbench().getDisplay();
        display.syncExec(new Runnable() {
            @Override
            public void run() {
                // Map of tree items whose key is apex class id and the value is the tree item
                Map<String, TreeItem> testClassNodes = Maps.newLinkedHashMap();

                FontRegistry registry = new FontRegistry();
                Font boldFont = registry.getBold(Display.getCurrent().getSystemFont().getFontData()[0].getName());

                runTestComposite.clearAllExceptProgress();
                Tree resultsTree = runTestComposite.getTree();

                // Add each test result to the tree
                for (ApexTestResult testResult : testResults) {
                    // Create or find the tree node for the test class
                    String classId = testResult.getApexClassId();
                    if (!testClassNodes.containsKey(classId)) {
                        TreeItem newClassNode = createTestClassTreeItem(resultsTree, testResources, boldFont,
                                classId);

                        testClassNodes.put(classId, newClassNode);
                    }

                    // Add the a test method tree node to the test class tree node
                    TreeItem classNode = testClassNodes.get(classId);
                    String className = classNode.getText();

                    // Create a tree item for the test method and save the test result
                    TreeItem newTestMethodNode = createTestMethodTreeItem(classNode, testResult, className);
                    // Set the color and icon of test method tree node based on test outcome
                    setColorAndIconForNode(newTestMethodNode, testResult.getOutcome());
                    // Update the color & icon of class tree node only if the test method
                    // outcome is worse than what the class tree node indicates
                    setColorAndIconForTheWorse(classNode, testResult.getOutcome());
                }

                if (expandFailedResults) {
                    // Expand the test classes that did not pass
                    expandProblematicTestClasses(resultsTree);
                }
            }
        });
    }

    /**
     * Update the UI with the test results for an synchronous test run.
     */
    @VisibleForTesting
    public void processSyncTestResults(final IProject project, final Map<IResource, List<String>> testResources,
            final RunTestsSyncResponse testResults) {
        if (Utils.isEmpty(project) || Utils.isEmpty(testResources) || Utils.isEmpty(testResults)) {
            return;
        }

        Display display = PlatformUI.getWorkbench().getDisplay();
        display.syncExec(new Runnable() {
            @Override
            public void run() {
                // Map of tree items whose key is apex class id and the value is the tree item
                Map<String, TreeItem> testClassNodes = Maps.newLinkedHashMap();

                FontRegistry registry = new FontRegistry();
                Font boldFont = registry.getBold(Display.getCurrent().getSystemFont().getFontData()[0].getName());

                // Reset tree
                Tree resultsTree = runTestComposite.getTree();
                resultsTree.removeAll();

                for (RunTestsSyncSuccess testPassed : testResults.getSuccesses()) {
                    // Create or find the tree node for the test class
                    final String classId = testPassed.getId();
                    final String className = testPassed.getName();
                    if (!testClassNodes.containsKey(classId)) {
                        TreeItem newClassNode = createTestClassTreeItem(resultsTree, testResources, boldFont,
                                classId);
                        testClassNodes.put(classId, newClassNode);
                    }

                    // Add the a test method tree node to the test class tree node
                    TreeItem classNode = testClassNodes.get(classId);
                    // Create a tree item for the test method and save the test result
                    TreeItem newTestMethodNode = createTestMethodTreeItem(classNode, className,
                            testPassed.getMethodName(), "", "", testResults.getApexLogId());
                    // Set the color and icon of test method tree node based on test outcome
                    setColorAndIconForNode(newTestMethodNode, ApexTestOutcome.Pass);
                }

                for (RunTestsSyncFailure testFailed : testResults.getFailures()) {
                    // Create or find the tree node for the test class
                    final String classId = testFailed.getId();
                    final String className = testFailed.getName();
                    if (!testClassNodes.containsKey(classId)) {
                        TreeItem newClassNode = createTestClassTreeItem(resultsTree, testResources, boldFont,
                                classId);
                        testClassNodes.put(classId, newClassNode);
                    }

                    // Add the a test method tree node to the test class tree node
                    TreeItem classNode = testClassNodes.get(classId);
                    // Create a tree item for the test method and save the test result
                    TreeItem newTestMethodNode = createTestMethodTreeItem(classNode, className,
                            testFailed.getMethodName(), testFailed.getMessage(), testFailed.getStackTrace(),
                            testResults.getApexLogId());
                    // Set the color and icon of test method tree node based on test outcome
                    setColorAndIconForNode(newTestMethodNode, ApexTestOutcome.Fail);
                    // Update the color & icon of class tree node only if the test method
                    // outcome is worse than what the class tree node indicates
                    setColorAndIconForTheWorse(classNode, ApexTestOutcome.Fail);
                }

                // Expand the test classes that did not pass
                expandProblematicTestClasses(resultsTree);
            }
        });
    }

    /**
     * Display org wide and individual class/trigger code coverage
     * from ApexOrgWideCoverage & ApexCodeCoverageAgg
     */
    @VisibleForTesting
    public void displayCodeCoverage() {
        if (Utils.isEmpty(forceProject) || Utils.isEmpty(runTestComposite))
            return;

        final List<CodeCovResult> ccResults = Lists.newArrayList();

        ApexOrgWideCoverage orgWide = getApexOrgWideCoverage();
        ApexCodeCoverageAggregateResponse codeCovs = getApexCodeCoverageAgg();

        IProject proj = forceProject.getProject();
        // Get a list of existing Apex classes & triggers
        List<IResource> resources = ApexSourceUtils.INSTANCE.findLocalSourcesInProject(proj);
        resources = ApexSourceUtils.INSTANCE.filterSourcesByClassOrTrigger(resources);

        // Save overall code coverage
        Integer orgWidePercent = Utils.isNotEmpty(orgWide) ? orgWide.getPercentCovered() : 0;
        CodeCovResult ccResult = new CodeCovResult(Messages.View_CodeCoverageOverall, null, orgWidePercent, null,
                null);
        ccResults.add(ccResult);

        for (Record codeCov : codeCovs.records) {
            // Get name, percent and lines covered
            final String classOrTriggerName = codeCov.ApexClassOrTrigger.Name;
            Integer linesCovered = codeCov.NumLinesCovered;
            Integer total = linesCovered + codeCov.NumLinesUncovered;
            Integer percent = (int) Math.round(linesCovered * 100.0 / total);

            // Find the correct resource for the given classOrTriggerName
            FluentIterable<IResource> curRes = FluentIterable.from(resources).filter(new Predicate<IResource>() {
                @Override
                public boolean apply(IResource res) {
                    if (res.getName().contains(classOrTriggerName)) {
                        return true;
                    }

                    return false;
                }
            });

            // Show code coverage markers on Apex class/trigger
            if (curRes != null && !curRes.isEmpty()) {
                try {
                    // Save code coverage info with resource
                    ApexCodeLocation location = findClass(curRes.get(0));
                    ccResults.add(new CodeCovResult(classOrTriggerName, location, percent, linesCovered, total));
                    applyCodeCoverageMarker(curRes.get(0), codeCov.Coverage.uncoveredLines);
                } catch (CoreException | BadLocationException e) {
                    logger.error("Failed to apply code coverage warnings for " + classOrTriggerName, e);
                }
            } else {
                // Save code coverage info without resource
                ccResults.add(new CodeCovResult(classOrTriggerName, null, percent, linesCovered, total));
                logger.error(String.format("Failed to find resource %s for code coverage", classOrTriggerName));
            }
        }

        Display display = PlatformUI.getWorkbench().getDisplay();
        display.syncExec(new Runnable() {
            @Override
            public void run() {
                // Update Apex Test Results view with code coverage
                runTestComposite.setCodeCoverage(ccResults);
            }
        });
    }

    /**
     * For each uncovered line in the resource, find the start and end of the line
     * and annotate it as not covered.
     */
    private void applyCodeCoverageMarker(IResource resource, List<Integer> uncoveredLines)
            throws CoreException, BadLocationException {
        IFile iFile = (IFile) resource;
        ITextFileBufferManager iTextFileBufferManager = FileBuffers.getTextFileBufferManager();
        iTextFileBufferManager.connect(iFile.getFullPath(), LocationKind.IFILE, new NullProgressMonitor());
        ITextFileBuffer iTextFileBuffer = iTextFileBufferManager.getTextFileBuffer(iFile.getFullPath(),
                LocationKind.IFILE);
        IDocument iDoc = iTextFileBuffer.getDocument();
        iTextFileBufferManager.disconnect(iFile.getFullPath(), LocationKind.IFILE, new NullProgressMonitor());

        for (Integer uncoveredLine : uncoveredLines) {
            int start = iDoc.getLineOffset(uncoveredLine - 1);
            int end = iDoc.getLineLength(uncoveredLine - 1);

            MarkerUtils.getInstance().applyCodeCoverageWarningMarker(resource, uncoveredLine, start, start + end,
                    Messages.View_LineNotCovered);
        }
    }

    /**
     * Create a default TreeItem for a test class.
     * @return TreeItem for test class
     */
    private TreeItem createTestClassTreeItem(Tree parent, Map<IResource, List<String>> testResources, Font font,
            String classId) {
        TreeItem newClassNode = new TreeItem(parent, SWT.NONE);
        newClassNode.setFont(font);
        newClassNode.setExpanded(false);
        // Mark this test class as pass until we find a test method within it that says otherwise
        setColorAndIconForNode(newClassNode, ApexTestOutcome.Pass);

        // Test result only has test class ID. Find the test class name mapped to that ID to display in UI
        IResource testResource = getResourceFromId(testResources, classId);
        String className = Utils.isNotEmpty(testResource) ? testResource.getName() : classId;
        newClassNode.setText(className);

        // Save the associated file in the tree item
        if (Utils.isNotEmpty(testResource)) {
            // For test classes, point to the class declaratio. Fallback to the first
            // line & column
            ApexCodeLocation location = findClass(testResource);
            newClassNode.setData(RunTestsConstants.TREEDATA_CODE_LOCATION, location);
            Map<String, ApexCodeLocation> testMethodLocs = findTestMethods(testResource);
            if (testMethodLocs != null && testMethodLocs.size() > 0) {
                newClassNode.setData(RunTestsConstants.TREEDATA_TEST_METHOD_LOCS, testMethodLocs);
            }
        }

        return newClassNode;
    }

    /**
     * Set color and icon for a test method's TreeItem.
     */
    private void setColorAndIconForNode(TreeItem node, ApexTestOutcome outcome) {
        if (Utils.isEmpty(node) || Utils.isEmpty(outcome))
            return;

        Display display = node.getDisplay();
        if (outcome.equals(ApexTestOutcome.Pass)) {
            node.setForeground(display.getSystemColor(RunTestsConstants.PASS_COLOR));
            node.setImage(RunTestsConstants.PASS_ICON);
        } else if (outcome.equals(ApexTestOutcome.Skip)) {
            node.setForeground(display.getSystemColor(RunTestsConstants.WARNING_COLOR));
            node.setImage(RunTestsConstants.WARNING_ICON);
        } else {
            node.setForeground(display.getSystemColor(RunTestsConstants.FAILURE_COLOR));
            node.setImage(RunTestsConstants.FAILURE_ICON);
        }
    }

    /**
     * Update the color & icon of a TreeItem only if the given outcome is worse than
     * what the TreeItem already indicates.
     */
    private void setColorAndIconForTheWorse(TreeItem node, ApexTestOutcome outcome) {
        if (Utils.isEmpty(node) || Utils.isEmpty(outcome))
            return;

        Image curImage = node.getImage();
        boolean worseThanPass = curImage.equals(RunTestsConstants.PASS_ICON)
                && !outcome.equals(ApexTestOutcome.Pass);
        boolean worseThanWarning = curImage.equals(RunTestsConstants.WARNING_ICON)
                && !outcome.equals(ApexTestOutcome.Pass) && !outcome.equals(ApexTestOutcome.Skip);
        if (worseThanPass || worseThanWarning) {
            setColorAndIconForNode(node, outcome);
        }
    }

    private ApexCodeLocation findClass(IResource resource) {
        return ApexSourceUtils.INSTANCE.findClassLocInFile(resource);
    }

    @VisibleForTesting
    public Map<IResource, List<String>> findTestClasses(IProject project) {
        return ApexSourceUtils.INSTANCE.findTestClassesInProject(project);
    }

    private Map<String, ApexCodeLocation> findTestMethods(IResource resource) {
        return ApexSourceUtils.INSTANCE.findTestMethodLocsInFile(resource);
    }

    /**
     * Create a TreeItem for a test method from an async test run.
     * @return TreeItem for test method
     */
    private TreeItem createTestMethodTreeItem(TreeItem classNode, ApexTestResult testResult, String className) {
        TreeItem newTestMethodNode = new TreeItem(classNode, SWT.NONE);
        newTestMethodNode.setText(testResult.getMethodName());
        newTestMethodNode.setData(RunTestsConstants.TREEDATA_APEX_LOG_ID, testResult.getApexLogId());
        newTestMethodNode.setData(RunTestsConstants.TREEDATA_RESULT_MESSAGE, testResult.getMessage());
        newTestMethodNode.setData(RunTestsConstants.TREEDATA_RESULT_STACKTRACE, testResult.getStackTrace());

        ApexCodeLocation location = getCodeLocationForTestMethod(newTestMethodNode, classNode, className,
                testResult.getMethodName(), testResult.getStackTrace());
        newTestMethodNode.setData(RunTestsConstants.TREEDATA_CODE_LOCATION, location);

        return newTestMethodNode;
    }

    /**
     * Create a TreeItem for a test method from an sync test run
     * @return TreeItem for test method
     */
    private TreeItem createTestMethodTreeItem(TreeItem classNode, String className, String methodName,
            String message, String stackTrace, String apexLogId) {
        TreeItem newTestMethodNode = new TreeItem(classNode, SWT.NONE);
        newTestMethodNode.setText(methodName);
        newTestMethodNode.setData(RunTestsConstants.TREEDATA_APEX_LOG_ID, apexLogId);
        newTestMethodNode.setData(RunTestsConstants.TREEDATA_RESULT_MESSAGE, message);
        newTestMethodNode.setData(RunTestsConstants.TREEDATA_RESULT_STACKTRACE, stackTrace);

        ApexCodeLocation location = getCodeLocationForTestMethod(newTestMethodNode, classNode, className,
                methodName, stackTrace);
        newTestMethodNode.setData(RunTestsConstants.TREEDATA_CODE_LOCATION, location);

        return newTestMethodNode;
    }

    /**
     * Get the code location of a test method. If there isn't one, we default to
     * the code location of the test class.
     * @return ApexCodeLocation
     */
    private ApexCodeLocation getCodeLocationForTestMethod(TreeItem methodNode, TreeItem classNode, String className,
            String methodName, String stackTrace) {
        @SuppressWarnings("unchecked")
        Map<String, ApexCodeLocation> testMethodLocs = (Map<String, ApexCodeLocation>) classNode
                .getData(RunTestsConstants.TREEDATA_TEST_METHOD_LOCS);
        ApexCodeLocation tmLocation = (testMethodLocs != null && testMethodLocs.containsKey(methodName))
                ? testMethodLocs.get(methodName)
                : getLocationFromStackLine(methodName, stackTrace);
        ApexCodeLocation tcLocation = (ApexCodeLocation) methodNode.getParentItem()
                .getData(RunTestsConstants.TREEDATA_CODE_LOCATION);
        // If there is no test method location, best effort is to use test class location
        if (Utils.isEmpty(tmLocation)) {
            tmLocation = tcLocation;
        } else {
            IFile file = tcLocation.getFile();
            tmLocation.setFile(file);
        }

        return tmLocation;
    }

    /**
     * Get line and column from a stack trace.
     * @return ApexCodeLocation
     */
    private ApexCodeLocation getLocationFromStackLine(String name, String stackTrace) {
        if (Utils.isEmpty(name) || Utils.isEmpty(stackTrace))
            return null;

        String line = null;
        String column = null;
        try {
            String[] temp = stackTrace.split("line");
            line = temp[1].split(",")[0].trim();
            String c = temp[1].trim();
            column = c.split("column")[1].trim();
            if (Utils.isNotEmpty(column) && column.contains("\n")) {
                column = column.substring(0, column.indexOf("\n"));
            }
        } catch (Exception e) {
        }

        return new ApexCodeLocation(name, line, column);
    }

    /**
     * Find a resource and convert to a file.
     * @return A source file
     */
    @VisibleForTesting
    public IResource getResourceFromId(Map<IResource, List<String>> testResources, String classID) {
        if (Utils.isNotEmpty(classID) && Utils.isNotEmpty(testResources)) {
            for (IResource testResource : testResources.keySet()) {
                String resourceId = ResourceProperties.getProperty(testResource, QualifiedNames.QN_ID);
                if (resourceId.equals(classID)) {
                    return testResource;
                }
            }
        }

        return null;
    }

    /**
     * Expand the TreeItems that did not pass.
     */
    private void expandProblematicTestClasses(Tree resultsTree) {
        if (Utils.isEmpty(resultsTree))
            return;

        for (TreeItem classNode : resultsTree.getItems()) {
            if (!classNode.getImage().equals(RunTestsConstants.PASS_ICON)) {
                classNode.setExpanded(true);
            }
        }
    }

    /**
     * Jump to and highlight a line based on the ApexCodeLocation.
     */
    @VisibleForTesting
    public void highlightLine(ApexCodeLocation location) {
        if (Utils.isEmpty(location) || location.getFile() == null || !location.getFile().exists()) {
            throwErrorMsg(Messages.View_ErrorOpenSourceTitle, Messages.View_ErrorOpenSourceSolution);
            return;
        }

        Map<String, Integer> map = Maps.newHashMap();
        map.put(IMarker.LINE_NUMBER, location.getLine());
        try {
            IMarker marker = location.getFile().createMarker(IMarker.TEXT);
            marker.setAttributes(map);
            IDE.openEditor(getSite().getWorkbenchWindow().getActivePage(), marker);
            marker.delete();
        } catch (Exception e) {
            logger.error("Unable to highlight line", e);
            throwErrorMsg(Messages.View_ErrorOpenSourceTitle, Messages.View_ErrorOpenSourceSolution);
        }
    }

    /**
     * Update the test results tabs.
     */
    public void updateView(TreeItem selectedTreeItem, String selectedTab, boolean openSource) {
        if (Utils.isEmpty(selectedTreeItem) || Utils.isEmpty(selectedTab) || Utils.isEmpty(runTestComposite)) {
            return;
        }

        // Only clear the right side because user will either select an item from the results tree
        // or a tab. We do not want to clear the tree (on the left side).
        runTestComposite.clearTabs();

        // Selecting a tab gives user more details on the test result, but for that to happen, 
        // they must select the test from the results tree first, which is when we will get 
        // the code location and open the source.
        if (openSource) {
            ApexCodeLocation location = (ApexCodeLocation) selectedTreeItem
                    .getData(RunTestsConstants.TREEDATA_CODE_LOCATION);
            highlightLine(location);
        }

        // Get the test result
        String apexLogId = (String) selectedTreeItem.getData(RunTestsConstants.TREEDATA_APEX_LOG_ID);
        String errorMessage = (String) selectedTreeItem.getData(RunTestsConstants.TREEDATA_RESULT_MESSAGE);
        String stackTrace = (String) selectedTreeItem.getData(RunTestsConstants.TREEDATA_RESULT_STACKTRACE);

        // Check which tab is in focus so we can update lazily
        if (selectedTab.equals(Messages.View_StackTrace)) {
            // Stack trace only exists in a test failure
            showStackTrace(errorMessage, stackTrace);
        } else if (this.shouldCreateTraceFlag && selectedTab.equals(Messages.View_SystemLog)) {
            String apexLog = tryToGetApexLog(selectedTreeItem, apexLogId);
            showSystemLog(apexLog);
        } else if (this.shouldCreateTraceFlag && selectedTab.equals(Messages.View_UserLog)) {
            String apexLog = tryToGetApexLog(selectedTreeItem, apexLogId);
            showUserLog(selectedTreeItem, apexLog);
        }
    }

    /**
    * Query an ApexLog with the specified log ID.
    * @return ApexLog
    */
    private ApexLog getApexLog(ForceProject forceProject, String logId) {
        try {
            initializeConnection(forceProject);

            QueryResult qr = toolingStubExt.query(String.format(RunTestsConstants.QUERY_APEX_LOG, logId));
            if (qr != null && qr.getSize() == 1) {
                ApexLog apexLog = (ApexLog) qr.getRecords()[0];
                return apexLog;
            }
        } catch (Exception e) {
            logger.error("Failed to get Apex log", e);
        }

        return null;
    }

    /**
     * Fetch the raw body of an ApexLog with the specified log ID.
     * @return Raw log. Null if something is wrong.
     */
    private String getApexLogBody(ForceProject forceProject, String logId) {
        String rawLog = null;

        try {
            initializeConnection(forceProject);

            ApexLogCommand job = new ApexLogCommand(new HTTPAdapter<>(String.class,
                    new ApexLogTransport(toolingRESTConnection, logId), HTTPMethod.GET));
            job.schedule();

            try {
                job.join();
                rawLog = job.getAnswer();

                if (job.wasError()) {
                    logger.error(String.format("Failed to get Apex Log. Error message: %s", job.getErrorMsg()));
                }
            } catch (InterruptedException e) {
                logger.error("Failed to get Apex Log", e);
            }
        } catch (Exception e) {
            logger.error("Failed to connect to Tooling API", e);
        }

        return rawLog;
    }

    /**
     * Get the body of an ApexLog. If that fails, get the toString of an ApexLog.
     * @return A string representation of an ApexLog
     */
    private String tryToGetApexLog(TreeItem selectedTreeItem, String logId) {
        if (Utils.isEmpty(forceProject) || Utils.isEmpty(selectedTreeItem) || Utils.isEmpty(logId))
            return null;

        // Do we already have the log body?
        try {
            String apexLogBody = (String) selectedTreeItem.getData(RunTestsConstants.TREEDATA_APEX_LOG_BODY);
            if (Utils.isNotEmpty(apexLogBody)) {
                return apexLogBody;
            }
        } catch (Exception e) {
        }

        // Try to get the log body
        String apexLogBody = getApexLogBody(forceProject, logId);
        if (Utils.isNotEmpty(apexLogBody)) {
            // Save it for future uses
            selectedTreeItem.setData(RunTestsConstants.TREEDATA_APEX_LOG_BODY, apexLogBody);
            return apexLogBody;
        }

        // There is no ApexLog body, so try to retrieve a saved ApexLog
        try {
            ApexLog apexLog = (ApexLog) selectedTreeItem.getData(RunTestsConstants.TREEDATA_APEX_LOG);
            if (Utils.isNotEmpty(apexLog)) {
                return apexLog.toString();
            }
        } catch (Exception e) {
        }

        // Try to get the ApexLog object
        ApexLog apexLog = getApexLog(forceProject, logId);
        selectedTreeItem.setData(RunTestsConstants.TREEDATA_APEX_LOG, apexLog);
        return (Utils.isNotEmpty(apexLog) ? apexLog.toString() : null);
    }

    /**
     * Update the Stack Trace tab with the given error message & stack trace.
     */
    private void showStackTrace(String message, String stackTrace) {
        if (Utils.isNotEmpty(runTestComposite)) {
            StringBuilder data = new StringBuilder();

            if (Utils.isNotEmpty(message)) {
                data.append(message + RunTestsConstants.NEW_LINE + RunTestsConstants.NEW_LINE);
            }

            if (Utils.isNotEmpty(stackTrace)) {
                data.append(stackTrace);
            }

            runTestComposite.setStackTraceArea(data.toString());
        }
    }

    /**
     * Update the System Debug Log tab with the given log.
     */
    private void showSystemLog(String log) {
        if (Utils.isNotEmpty(runTestComposite) && Utils.isNotEmpty(log)) {
            runTestComposite.setSystemLogsTextArea(log);
        }
    }

    /**
     * Update the User Debug Log tab with a filtered log.
     */
    private void showUserLog(TreeItem selectedTreeItem, String log) {
        if (Utils.isEmpty(selectedTreeItem) || Utils.isEmpty(runTestComposite)) {
            return;
        }

        // Do we already have a filtered log?
        try {
            String userDebugLog = (String) selectedTreeItem.getData(RunTestsConstants.TREEDATA_APEX_LOG_USER_DEBUG);
            if (Utils.isNotEmpty(userDebugLog)) {
                runTestComposite.setUserLogsTextArea(userDebugLog);
                return;
            }
        } catch (Exception e) {
        }

        // Filter the given log with only DEBUG statements
        if (Utils.isNotEmpty(log) && log.contains("DEBUG")) {
            String userDebugLog = "";
            String[] newDateWithSperators = log.split("\\|");
            for (int index = 0; index < newDateWithSperators.length; index++) {
                String newDateWithSperator = newDateWithSperators[index];
                if (newDateWithSperator.contains("USER_DEBUG")) {
                    String debugData = newDateWithSperators[index + 3];
                    debugData = debugData.substring(0, debugData.lastIndexOf('\n'));
                    userDebugLog += "\n" + debugData + "\n";
                }
            }
            // Save it for future uses
            selectedTreeItem.setData(RunTestsConstants.TREEDATA_APEX_LOG_USER_DEBUG, userDebugLog);
            // Update the tab
            runTestComposite.setUserLogsTextArea(userDebugLog);
        }
    }

    /**
     * Get the code coverage aggregate.
     */
    private ApexCodeCoverageAggregateResponse getApexCodeCoverageAgg() {
        ApexCodeCoverageAggregateResponse results = null;
        String response = "";

        try {
            initializeConnection(forceProject);

            ToolingQueryCommand job = getQueryJob(RunTestsConstants.QUERY_APEX_CODE_COVERAGE_AGG);
            job.schedule();

            job.join();
            response = job.getAnswer();

            if (job.wasError()) {
                logger.error(String.format("Failed to run query: %s. Error message: %s",
                        RunTestsConstants.QUERY_APEX_CODE_COVERAGE_AGG, job.getErrorMsg()));
            }

            ObjectMapper mapper = new ObjectMapper();
            results = mapper.readValue(response, ApexCodeCoverageAggregateResponse.class);
        } catch (Exception e) {
            logger.error("Failed to get ApexCodeCoverageAggregate. Response is " + response, e);
        }

        return results;
    }

    /**
     * Query Tooling API.
     * @return Promiseable job
     */
    private ToolingQueryCommand getQueryJob(String query) {
        return new ToolingQueryCommand(new HTTPAdapter<>(String.class,
                new ToolingQueryTransport(toolingRESTConnection, query), HTTPMethod.GET));
    }

    /**
     * Get the org wide code coverage.
     * @return ApexOrgWideCoverage
     */
    private ApexOrgWideCoverage getApexOrgWideCoverage() {
        try {
            initializeConnection(forceProject);

            QueryResult qr = toolingStubExt.query(RunTestsConstants.QUERY_APEX_ORG_WIDE_COVERAGE);
            if (qr != null && qr.getSize() == 1) {
                ApexOrgWideCoverage orgWideCov = (ApexOrgWideCoverage) qr.getRecords()[0];
                return orgWideCov;
            }
        } catch (Exception e) {
            logger.error("Failed to get ApexOrgWideCoverage", e);
        }

        return null;
    }

    /**
    * Initialize Tooling connection.
    */
    @VisibleForTesting
    public void initializeConnection(ForceProject forceProject)
            throws ForceConnectionException, ForceRemoteException {
        toolingRESTConnection = new HTTPConnection(forceProject, RunTestsConstants.TOOLING_ENDPOINT);
        toolingRESTConnection.initialize();
        toolingStubExt = ContainerDelegate.getInstance().getFactoryLocator().getToolingFactory()
                .getToolingStubExt(forceProject);
    }

    /**
     * Initialize Tooling Connection with timeout.
     */
    @VisibleForTesting
    public void initializeConnection(ForceProject forceProject, int timeout)
            throws ForceConnectionException, ForceRemoteException {
        toolingRESTConnection = new HTTPConnection(forceProject, RunTestsConstants.TOOLING_ENDPOINT, timeout);
        toolingRESTConnection.initialize();
        toolingStubExt = ContainerDelegate.getInstance().getFactoryLocator().getToolingFactory()
                .getToolingStubExt(forceProject);
    }

    private void setProject(IProject project) {
        this.project = project;
        this.runTestComposite.setProject(this.project);
    }

    @VisibleForTesting
    public RunTestsViewComposite getRunTestComposite() {
        return runTestComposite;
    }

    @Override
    public void dispose() {
        super.dispose();
        getSite().getPage().removeSelectionListener(fPostSelectionListener);
    }

    @Override
    public void createPartControl(Composite parent) {
        runTestComposite = new RunTestsViewComposite(parent, SWT.NONE, this);
        setPartName(Messages.View_Name);
        setTitleImage(getImage());

        UIUtils.setHelpContext(runTestComposite, this.getClass().getSimpleName());
    }

    @Override
    public void setFocus() {
        if (Utils.isNotEmpty(runTestComposite)) {
            runTestComposite.setFocus();
        }
    }

    private void setSelectionListener() {
        fPostSelectionListener = new ISelectionListener() {
            @Override
            public void selectionChanged(IWorkbenchPart part, ISelection selection) {
                project = getProjectService().getProject(selection);
                if (selection instanceof IStructuredSelection) {
                    IStructuredSelection ss = (IStructuredSelection) selection;
                    Object selElement = ss.getFirstElement();
                    if (selElement instanceof IResource) {
                        setProject(((IResource) selElement).getProject());
                    }
                }
            }
        };
    }
}