Bug 529321 - [testsources][launching] Add option to exclude test code
from runtime classpath

Change-Id: I1067077dfe1eaf2315552182c717e2bf0f700149
diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/debug/ui/launchConfigurations/JavaApplicationLaunchShortcut.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/debug/ui/launchConfigurations/JavaApplicationLaunchShortcut.java
index 9ac5acd..f0a921f 100644
--- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/debug/ui/launchConfigurations/JavaApplicationLaunchShortcut.java
+++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/debug/ui/launchConfigurations/JavaApplicationLaunchShortcut.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2007, 2015 IBM Corporation and others.
+ * Copyright (c) 2007, 2018 IBM Corporation and others.
  * 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
@@ -30,6 +30,7 @@
 import org.eclipse.jdt.core.IMethod;
 import org.eclipse.jdt.core.IType;
 import org.eclipse.jdt.core.JavaModelException;
+import org.eclipse.jdt.core.provisional.JavaModelAccess;
 import org.eclipse.jdt.core.search.IJavaSearchScope;
 import org.eclipse.jdt.core.search.SearchEngine;
 import org.eclipse.jdt.internal.debug.ui.JDIDebugUIPlugin;
@@ -89,6 +90,9 @@
 			wc = configType.newInstance(null, getLaunchManager().generateLaunchConfigurationName(type.getTypeQualifiedName('.')));
 			wc.setAttribute(IJavaLaunchConfigurationConstants.ATTR_MAIN_TYPE_NAME, type.getFullyQualifiedName());
 			wc.setAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, type.getJavaProject().getElementName());
+			if (!JavaModelAccess.isTestCode(type)) {
+				wc.setAttribute(IJavaLaunchConfigurationConstants.ATTR_EXCLUDE_TEST_CODE, true);
+			}
 			wc.setMappedResources(new IResource[] {type.getUnderlyingResource()});
 			config = wc.doSave();
 		} catch (CoreException exception) {
diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/debug/ui/launchConfigurations/JavaClasspathTab.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/debug/ui/launchConfigurations/JavaClasspathTab.java
index 1f6e843..ff7f8f5 100644
--- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/debug/ui/launchConfigurations/JavaClasspathTab.java
+++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/debug/ui/launchConfigurations/JavaClasspathTab.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2017 IBM Corporation and others.
+ * Copyright (c) 2000, 2018 IBM Corporation and others.
  * 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
@@ -24,6 +24,7 @@
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.debug.core.ILaunchConfiguration;
 import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
+import org.eclipse.debug.internal.ui.SWTFactory;
 import org.eclipse.jdt.internal.debug.ui.IJavaDebugHelpContextIds;
 import org.eclipse.jdt.internal.debug.ui.JDIDebugUIPlugin;
 import org.eclipse.jdt.internal.debug.ui.JavaDebugImages;
@@ -57,6 +58,8 @@
 import org.eclipse.jface.action.IAction;
 import org.eclipse.osgi.util.NLS;
 import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
 import org.eclipse.swt.graphics.Font;
 import org.eclipse.swt.graphics.Image;
 import org.eclipse.swt.layout.GridData;
@@ -92,6 +95,8 @@
 	 */
 	protected ILaunchConfiguration fLaunchConfiguration;
 
+	private Button fExcludeTestCodeButton;
+
 	/**
 	 * Constructor
 	 */
@@ -140,6 +145,16 @@
 		pathButtonComp.setFont(font);
 
 		createPathButtons(pathButtonComp);
+		SWTFactory.createVerticalSpacer(comp, 2);
+
+		fExcludeTestCodeButton = SWTFactory.createCheckButton(comp, LauncherMessages.JavaClasspathTab_Exclude_Test_Code, null, false, 2);
+		fExcludeTestCodeButton.addSelectionListener(new SelectionAdapter() {
+			@Override
+			public void widgetSelected(SelectionEvent evt) {
+				setDirty(true);
+				updateLaunchConfigurationDialog();
+			}
+		});
 	}
 
 	/**
@@ -211,6 +226,10 @@
 	public void initializeFrom(ILaunchConfiguration configuration) {
 		refresh(configuration);
 		fClasspathViewer.getTreeViewer().expandToLevel(2);
+		try {
+			fExcludeTestCodeButton.setSelection(configuration.getAttribute(IJavaLaunchConfigurationConstants.ATTR_EXCLUDE_TEST_CODE, false));
+		} catch (CoreException e) {
+		}
 	}
 
 	/* (non-Javadoc)
@@ -292,6 +311,16 @@
 					JDIDebugUIPlugin.statusDialog(LauncherMessages.JavaClasspathTab_Unable_to_save_classpath_1, e.getStatus());
 				}
 			}
+			try {
+				boolean previousExcludeTestCode = configuration.getAttribute(IJavaLaunchConfigurationConstants.ATTR_EXCLUDE_TEST_CODE, false);
+				if (previousExcludeTestCode != fExcludeTestCodeButton.getSelection()) {
+					configuration.setAttribute(IJavaLaunchConfigurationConstants.ATTR_EXCLUDE_TEST_CODE, fExcludeTestCodeButton.getSelection());
+					fClasspathViewer.setEntries(JavaRuntime.computeUnresolvedRuntimeClasspath(configuration));
+				}
+			}
+			catch (CoreException e) {
+				JDIDebugUIPlugin.statusDialog(LauncherMessages.JavaClasspathTab_Unable_to_save_classpath_1, e.getStatus());
+			}
 		}
 	}
 
diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/debug/ui/launchConfigurations/JavaDependenciesTab.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/debug/ui/launchConfigurations/JavaDependenciesTab.java
index bb6fa4c..0cf229f 100644
--- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/debug/ui/launchConfigurations/JavaDependenciesTab.java
+++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/debug/ui/launchConfigurations/JavaDependenciesTab.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2017 IBM Corporation and others.
+ * Copyright (c) 2018 IBM Corporation and others.
  * 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
@@ -22,6 +22,7 @@
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.debug.core.ILaunchConfiguration;
 import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
+import org.eclipse.debug.internal.ui.SWTFactory;
 import org.eclipse.jdt.internal.debug.ui.IJavaDebugHelpContextIds;
 import org.eclipse.jdt.internal.debug.ui.JDIDebugUIPlugin;
 import org.eclipse.jdt.internal.debug.ui.JavaDebugImages;
@@ -55,6 +56,8 @@
 import org.eclipse.jface.action.IAction;
 import org.eclipse.osgi.util.NLS;
 import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
 import org.eclipse.swt.graphics.Font;
 import org.eclipse.swt.graphics.Image;
 import org.eclipse.swt.layout.GridData;
@@ -88,6 +91,8 @@
 	 */
 	protected ILaunchConfiguration fLaunchConfiguration;
 
+	private Button fExcludeTestCodeButton;
+
 	/**
 	 * Constructor
 	 */
@@ -135,6 +140,17 @@
 		pathButtonComp.setFont(font);
 
 		createPathButtons(pathButtonComp);
+
+		SWTFactory.createVerticalSpacer(comp, 2);
+
+		fExcludeTestCodeButton = SWTFactory.createCheckButton(comp, LauncherMessages.JavaClasspathTab_Exclude_Test_Code, null, false, 2);
+		fExcludeTestCodeButton.addSelectionListener(new SelectionAdapter() {
+			@Override
+			public void widgetSelected(SelectionEvent evt) {
+				setDirty(true);
+				updateLaunchConfigurationDialog();
+			}
+		});
 	}
 
 	/**
@@ -210,6 +226,10 @@
 	public void initializeFrom(ILaunchConfiguration configuration) {
 		refresh(configuration);
 		fClasspathViewer.getTreeViewer().expandToLevel(2);
+		try {
+			fExcludeTestCodeButton.setSelection(configuration.getAttribute(IJavaLaunchConfigurationConstants.ATTR_EXCLUDE_TEST_CODE, false));
+		} catch (CoreException e) {
+		}
 	}
 
 	/* (non-Javadoc)
@@ -301,6 +321,16 @@
 					JDIDebugUIPlugin.statusDialog(LauncherMessages.JavaClasspathTab_Unable_to_save_classpath_1, e.getStatus());
 				}
 			}
+			try {
+				boolean previousExcludeTestCode = configuration.getAttribute(IJavaLaunchConfigurationConstants.ATTR_EXCLUDE_TEST_CODE, false);
+				if (previousExcludeTestCode != fExcludeTestCodeButton.getSelection()) {
+					configuration.setAttribute(IJavaLaunchConfigurationConstants.ATTR_EXCLUDE_TEST_CODE, fExcludeTestCodeButton.getSelection());
+					fClasspathViewer.setEntries(JavaRuntime.computeUnresolvedRuntimeClasspath(configuration));
+				}
+			}
+			catch (CoreException e) {
+				JDIDebugUIPlugin.statusDialog(LauncherMessages.JavaClasspathTab_Unable_to_save_classpath_1, e.getStatus());
+			}
 		}
 	}
 
@@ -361,6 +391,19 @@
 			ILaunchConfigurationWorkingCopy wc = configuration.getWorkingCopy();
 			wc.setAttribute(IJavaLaunchConfigurationConstants.ATTR_DEFAULT_CLASSPATH, true);
 			IRuntimeClasspathEntry[] entries= JavaRuntime.computeUnresolvedRuntimeClasspath(wc);
+			ArrayList<IRuntimeClasspathEntry> grouped = new ArrayList<>(entries.length);
+			// move all modulepath entries to the front, like in the ui
+			for (IRuntimeClasspathEntry entry : entries) {
+				if (entry.getClasspathProperty() == IRuntimeClasspathEntry.MODULE_PATH) {
+					grouped.add(entry);
+				}
+			}
+			for (IRuntimeClasspathEntry entry : entries) {
+				if (entry.getClasspathProperty() != IRuntimeClasspathEntry.MODULE_PATH) {
+					grouped.add(entry);
+				}
+			}
+			entries = grouped.toArray(new IRuntimeClasspathEntry[grouped.size()]);
 			if (classpath.length == entries.length) {
 				for (int i = 0; i < entries.length; i++) {
 					IRuntimeClasspathEntry entry = entries[i];
diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/launcher/LauncherMessages.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/launcher/LauncherMessages.java
index 55dab90..c2fd9e5 100644
--- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/launcher/LauncherMessages.java
+++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/launcher/LauncherMessages.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- *  Copyright (c) 2000, 2017 IBM Corporation and others.
+ *  Copyright (c) 2000, 2018 IBM Corporation and others.
  *  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
@@ -107,6 +107,7 @@
 	public static String JavaClasspathTab_0;
 	public static String JavaClasspathTab_Unable_to_save_classpath_1;
 	public static String JavaClasspathTab_Invalid_runtime_classpath_1;
+	public static String JavaClasspathTab_Exclude_Test_Code;
 
 	public static String JavaClasspathTab_AttributeLabel_DefaultClasspath;
 	public static String JavaClasspathTab_AttributeLabel_Classpath;
diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/launcher/LauncherMessages.properties b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/launcher/LauncherMessages.properties
index d571ba7..8a67b3e 100644
--- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/launcher/LauncherMessages.properties
+++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/launcher/LauncherMessages.properties
@@ -1,5 +1,5 @@
 ###############################################################################
-#  Copyright (c) 2000, 2017 IBM Corporation and others.
+#  Copyright (c) 2000, 2018 IBM Corporation and others.
 #  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
@@ -101,6 +101,7 @@
 JavaClasspathTab_1=Referenced project [{0}] is not accessible.
 JavaClasspathTab_Unable_to_save_classpath_1=Unable to save classpath
 JavaClasspathTab_Invalid_runtime_classpath_1=Archive does not exist: {0}
+JavaClasspathTab_Exclude_Test_Code=Exclude Test Code
 
 JavaClasspathTab_AttributeLabel_DefaultClasspath=Default classpath
 JavaClasspathTab_AttributeLabel_Classpath=Classpath
diff --git a/org.eclipse.jdt.launching/.settings/.api_filters b/org.eclipse.jdt.launching/.settings/.api_filters
index 0da98d6..e437596 100644
--- a/org.eclipse.jdt.launching/.settings/.api_filters
+++ b/org.eclipse.jdt.launching/.settings/.api_filters
@@ -1,5 +1,13 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <component id="org.eclipse.jdt.launching" version="2">
+    <resource path="launching/org/eclipse/jdt/launching/IRuntimeClasspathEntryResolver.java" type="org.eclipse.jdt.launching.IRuntimeClasspathEntryResolver">
+        <filter comment="New method is a default method that delegates to an existing method" id="404000815">
+            <message_arguments>
+                <message_argument value="org.eclipse.jdt.launching.IRuntimeClasspathEntryResolver"/>
+                <message_argument value="resolveRuntimeClasspathEntry(IRuntimeClasspathEntry, IJavaProject, boolean)"/>
+            </message_arguments>
+        </filter>
+    </resource>
     <resource path="launching/org/eclipse/jdt/launching/sourcelookup/LocalFileStorage.java" type="org.eclipse.jdt.launching.sourcelookup.LocalFileStorage">
         <filter comment="Known illegal extension" id="571473929">
             <message_arguments>
diff --git a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/DefaultEntryResolver.java b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/DefaultEntryResolver.java
index 90023ff..33f0db2 100644
--- a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/DefaultEntryResolver.java
+++ b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/DefaultEntryResolver.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2017 IBM Corporation and others.
+ * Copyright (c) 2000, 2018 IBM Corporation and others.
  * 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
@@ -49,11 +49,22 @@
 	 */
 	@Override
 	public IRuntimeClasspathEntry[] resolveRuntimeClasspathEntry(IRuntimeClasspathEntry entry, IJavaProject project) throws CoreException {
+		return resolveRuntimeClasspathEntry(entry, project, false);
+	}
+
+	/*
+	 * (non-Javadoc)
+	 *
+	 * @see org.eclipse.jdt.launching.IRuntimeClasspathEntryResolver#resolveRuntimeClasspathEntry(org.eclipse.jdt.launching.IRuntimeClasspathEntry,
+	 * org.eclipse.jdt.core.IJavaProject, boolean)
+	 */
+	@Override
+	public IRuntimeClasspathEntry[] resolveRuntimeClasspathEntry(IRuntimeClasspathEntry entry, IJavaProject project, boolean excludeTestCode) throws CoreException {
 		IRuntimeClasspathEntry2 entry2 = (IRuntimeClasspathEntry2)entry;
-		IRuntimeClasspathEntry[] entries = entry2.getRuntimeClasspathEntries(null);
+		IRuntimeClasspathEntry[] entries = entry2.getRuntimeClasspathEntries(excludeTestCode);
 		List<IRuntimeClasspathEntry> resolved = new ArrayList<>();
 		for (int i = 0; i < entries.length; i++) {
-			IRuntimeClasspathEntry[] temp = JavaRuntime.resolveRuntimeClasspathEntry(entries[i], project);
+			IRuntimeClasspathEntry[] temp = JavaRuntime.resolveRuntimeClasspathEntry(entries[i], project, excludeTestCode);
 			for (int j = 0; j < temp.length; j++) {
 				resolved.add(temp[j]);
 			}
diff --git a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/DefaultProjectClasspathEntry.java b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/DefaultProjectClasspathEntry.java
index 569eaa2..8dc2675 100644
--- a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/DefaultProjectClasspathEntry.java
+++ b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/DefaultProjectClasspathEntry.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- *  Copyright (c) 2000, 2017 IBM Corporation and others.
+ *  Copyright (c) 2000, 2018 IBM Corporation and others.
  *  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
@@ -27,6 +27,7 @@
 import org.eclipse.jdt.core.IJavaProject;
 import org.eclipse.jdt.core.IPackageFragmentRoot;
 import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
 import org.eclipse.jdt.launching.IRuntimeClasspathEntry;
 import org.eclipse.jdt.launching.IRuntimeContainerComparator;
 import org.eclipse.jdt.launching.JavaRuntime;
@@ -136,10 +137,22 @@
 	 */
 	@Override
 	public IRuntimeClasspathEntry[] getRuntimeClasspathEntries(ILaunchConfiguration configuration) throws CoreException {
+		boolean excludeTestCode = configuration != null
+				&& configuration.getAttribute(IJavaLaunchConfigurationConstants.ATTR_EXCLUDE_TEST_CODE, false);
+		return getRuntimeClasspathEntries(excludeTestCode);
+	}
+
+	/*
+	 * (non-Javadoc)
+	 *
+	 * @see org.eclipse.jdt.launching.IRuntimeClasspathEntry2#getRuntimeClasspathEntries(boolean)
+	 */
+	@Override
+	public IRuntimeClasspathEntry[] getRuntimeClasspathEntries(boolean excludeTestCode) throws CoreException {
 		IClasspathEntry entry = JavaCore.newProjectEntry(getJavaProject().getProject().getFullPath());
 		List<Object> classpathEntries = new ArrayList<>(5);
 		List<IClasspathEntry> expanding = new ArrayList<>(5);
-		expandProject(entry, classpathEntries, expanding);
+		expandProject(entry, classpathEntries, expanding, excludeTestCode);
 		IRuntimeClasspathEntry[] runtimeEntries = new IRuntimeClasspathEntry[classpathEntries.size()];
 		for (int i = 0; i < runtimeEntries.length; i++) {
 			Object e = classpathEntries.get(i);
@@ -161,17 +174,20 @@
 	}
 
 	/**
-	 * Returns the transitive closure of classpath entries for the
-	 * given project entry.
+	 * Returns the transitive closure of classpath entries for the given project entry.
 	 *
-	 * @param projectEntry project classpath entry
-	 * @param expandedPath a list of entries already expanded, should be empty
-	 * to begin, and contains the result
-	 * @param expanding a list of projects that have been or are currently being
-	 * expanded (to detect cycles)
-	 * @exception CoreException if unable to expand the classpath
+	 * @param projectEntry
+	 *            project classpath entry
+	 * @param expandedPath
+	 *            a list of entries already expanded, should be empty to begin, and contains the result
+	 * @param expanding
+	 *            a list of projects that have been or are currently being expanded (to detect cycles)
+	 * @param excludeTestCode
+	 *            if true, test dependencies will be excluded
+	 * @exception CoreException
+	 *                if unable to expand the classpath
 	 */
-	private void expandProject(IClasspathEntry projectEntry, List<Object> expandedPath, List<IClasspathEntry> expanding) throws CoreException {
+	private void expandProject(IClasspathEntry projectEntry, List<Object> expandedPath, List<IClasspathEntry> expanding, boolean excludeTestCode) throws CoreException {
 		expanding.add(projectEntry);
 		// 1. Get the raw classpath
 		// 2. Replace source folder entries with a project entry
@@ -194,6 +210,9 @@
 		boolean projectAdded = false;
 		for (int i = 0; i < buildPath.length; i++) {
 			IClasspathEntry classpathEntry = buildPath[i];
+			if (excludeTestCode && classpathEntry.isTest()) {
+				continue;
+			}
 			if (classpathEntry.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
 				if (!projectAdded) {
 					projectAdded = true;
@@ -220,7 +239,7 @@
 				switch (entry.getEntryKind()) {
 					case IClasspathEntry.CPE_PROJECT:
 						if (!expanding.contains(entry)) {
-							expandProject(entry, expandedPath, expanding);
+							expandProject(entry, expandedPath, expanding, excludeTestCode);
 						}
 						break;
 					case IClasspathEntry.CPE_CONTAINER:
diff --git a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/JRERuntimeClasspathEntryResolver.java b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/JRERuntimeClasspathEntryResolver.java
index 381c0c9..e9a90de 100644
--- a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/JRERuntimeClasspathEntryResolver.java
+++ b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/JRERuntimeClasspathEntryResolver.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2015 IBM Corporation and others.
+ * Copyright (c) 2000, 2018 IBM Corporation and others.
  * 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
diff --git a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/ProjectClasspathVariableResolver.java b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/ProjectClasspathVariableResolver.java
index 9980ff9..5083692 100644
--- a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/ProjectClasspathVariableResolver.java
+++ b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/ProjectClasspathVariableResolver.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2010, 2015 IBM Corporation and others.
+ * Copyright (c) 2010, 2018 IBM Corporation and others.
  * 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
@@ -55,10 +55,10 @@
 		IJavaProject javaProject = JavaCore.create(proj);
 		if (javaProject.exists()) {
 			IRuntimeClasspathEntry2 defClassPath = (IRuntimeClasspathEntry2) JavaRuntime.newDefaultProjectClasspathEntry(javaProject);
-			IRuntimeClasspathEntry[] entries = defClassPath.getRuntimeClasspathEntries(null);
+			IRuntimeClasspathEntry[] entries = defClassPath.getRuntimeClasspathEntries(false);
 			List<IRuntimeClasspathEntry> collect = new ArrayList<>();
 			for (int i = 0; i < entries.length; i++) {
-				IRuntimeClasspathEntry[] children = JavaRuntime.resolveRuntimeClasspathEntry(entries[i], javaProject);
+				IRuntimeClasspathEntry[] children = JavaRuntime.resolveRuntimeClasspathEntry(entries[i], javaProject, false);
 				for (int j = 0; j < children.length; j++) {
 					collect.add(children[j]);
 				}
diff --git a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/RuntimeClasspathEntryResolver.java b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/RuntimeClasspathEntryResolver.java
index 6906f1b..81cf38a 100644
--- a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/RuntimeClasspathEntryResolver.java
+++ b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/RuntimeClasspathEntryResolver.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2015 IBM Corporation and others.
+ * Copyright (c) 2000, 2018 IBM Corporation and others.
  * 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
@@ -99,6 +99,14 @@
 		return getResolver().resolveRuntimeClasspathEntry(entry, project);
 	}
 
+	/**
+	 * @see IRuntimeClasspathEntryResolver#resolveRuntimeClasspathEntry(IRuntimeClasspathEntry, IJavaProject, excludeTestCode)
+	 */
+	@Override
+	public IRuntimeClasspathEntry[] resolveRuntimeClasspathEntry(IRuntimeClasspathEntry entry, IJavaProject project, boolean excludeTestCode) throws CoreException {
+		return getResolver().resolveRuntimeClasspathEntry(entry, project, excludeTestCode);
+	}
+
 	/* (non-Javadoc)
 	 * @see org.eclipse.jdt.launching.IRuntimeClasspathEntryResolver2#isVMInstallReference(org.eclipse.jdt.core.IClasspathEntry)
 	 */
diff --git a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/VariableClasspathResolver.java b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/VariableClasspathResolver.java
index 53350df..580f1b3 100644
--- a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/VariableClasspathResolver.java
+++ b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/VariableClasspathResolver.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2015 IBM Corporation and others.
+ * Copyright (c) 2000, 2018 IBM Corporation and others.
  * 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
diff --git a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/IJavaLaunchConfigurationConstants.java b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/IJavaLaunchConfigurationConstants.java
index b036c93..5e910b6 100644
--- a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/IJavaLaunchConfigurationConstants.java
+++ b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/IJavaLaunchConfigurationConstants.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2017 IBM Corporation and others.
+ * Copyright (c) 2000, 2018 IBM Corporation and others.
  * 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
@@ -359,6 +359,15 @@
 	 * @since 3.7
 	 */
 	public static final String ATTR_USE_START_ON_FIRST_THREAD = LaunchingPlugin.getUniqueIdentifier() + ".ATTR_USE_START_ON_FIRST_THREAD"; //$NON-NLS-1$
+
+	/**
+	 * Launch configuration attribute key. The value is a boolean specifying whether output folders corresponding to test sources should not be added
+	 * to the runtime classpath and test dependencies should not be added to the default classpath.
+	 *
+	 * @since 3.10
+	 */
+	public static final String ATTR_EXCLUDE_TEST_CODE = LaunchingPlugin.getUniqueIdentifier() + ".ATTR_EXCLUDE_TEST_CODE"; //$NON-NLS-1$
+
 	/**
 	 * Status code indicating a launch configuration does not
 	 * specify a project when a project is required.
diff --git a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/IRuntimeClasspathEntry2.java b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/IRuntimeClasspathEntry2.java
index ad33f57..24e8055 100644
--- a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/IRuntimeClasspathEntry2.java
+++ b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/IRuntimeClasspathEntry2.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2008 IBM Corporation and others.
+ * Copyright (c) 2000, 2018 IBM Corporation and others.
  * 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
@@ -70,6 +70,20 @@
 	public IRuntimeClasspathEntry[] getRuntimeClasspathEntries(ILaunchConfiguration configuration) throws CoreException;
 
 	/**
+	 * Returns the classpath entries this entry is composed of, or an empty collection if this entry is not a composite entry.
+	 *
+	 * @param excludeTestCode
+	 *            true, if test code should be excluded
+	 * @return the classpath entries this entry is composed of, or an empty collection if this entry is not a composite entry
+	 * @throws CoreException
+	 *             if unable to retrieve contained entries
+	 * @since 3.10
+	 */
+	default public IRuntimeClasspathEntry[] getRuntimeClasspathEntries(boolean excludeTestCode) throws CoreException {
+		return getRuntimeClasspathEntries(null);
+	}
+
+	/**
 	 * Returns a human readable name for this classpath entry.
 	 *
 	 * @return a human readable name for this classpath entry
diff --git a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/IRuntimeClasspathEntryResolver.java b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/IRuntimeClasspathEntryResolver.java
index 95a86f9..036dc56 100644
--- a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/IRuntimeClasspathEntryResolver.java
+++ b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/IRuntimeClasspathEntryResolver.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2006 IBM Corporation and others.
+ * Copyright (c) 2000, 2018 IBM Corporation and others.
  * 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
@@ -86,12 +86,31 @@
 	public IRuntimeClasspathEntry[] resolveRuntimeClasspathEntry(IRuntimeClasspathEntry entry, IJavaProject project) throws CoreException;
 
 	/**
-	 * Returns a VM install associated with the given classpath entry,
-	 * or <code>null</code> if none.
+	 * Returns resolved runtime classpath entries for the given runtime classpath entry, in the context of the given Java project.
 	 *
-	 * @param entry classpath entry
+	 * @param entry
+	 *            runtime classpath entry to resolve, of type <code>VARIABLE</code> or <code>CONTAINTER</code>
+	 * @param project
+	 *            context in which the runtime classpath entry needs to be resolved
+	 * @param excludeTestCode
+	 *            when true, test code should be excluded
+	 * @return resolved entries (zero or more)
+	 * @exception CoreException
+	 *                if unable to resolve the entry
+	 * @since 3.10
+	 */
+	default public IRuntimeClasspathEntry[] resolveRuntimeClasspathEntry(IRuntimeClasspathEntry entry, IJavaProject project, boolean excludeTestCode) throws CoreException {
+		return resolveRuntimeClasspathEntry(entry, project);
+	}
+
+	/**
+	 * Returns a VM install associated with the given classpath entry, or <code>null</code> if none.
+	 *
+	 * @param entry
+	 *            classpath entry
 	 * @return vm install associated with entry or <code>null</code> if none
-	 * @exception CoreException if unable to resolve a VM
+	 * @exception CoreException
+	 *                if unable to resolve a VM
 	 */
 	public IVMInstall resolveVMInstall(IClasspathEntry entry) throws CoreException;
 }
diff --git a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/JavaRuntime.java b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/JavaRuntime.java
index 118909e..fc0ab78 100644
--- a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/JavaRuntime.java
+++ b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/JavaRuntime.java
@@ -840,16 +840,35 @@
 	}
 
 	/**
-	 * Computes and returns the default unresolved runtime classpath for the
-	 * given project.
+	 * Computes and returns the default unresolved runtime classpath for the given project.
 	 *
-	 * @param project the {@link IJavaProject} to compute the unresolved runtime classpath for
+	 * @param project
+	 *            the {@link IJavaProject} to compute the unresolved runtime classpath for
+	 * @param excludeTestCode
 	 * @return runtime classpath entries
-	 * @exception CoreException if unable to compute the runtime classpath
+	 * @exception CoreException
+	 *                if unable to compute the runtime classpath
 	 * @see IRuntimeClasspathEntry
 	 * @since 2.0
 	 */
 	public static IRuntimeClasspathEntry[] computeUnresolvedRuntimeClasspath(IJavaProject project) throws CoreException {
+		return computeUnresolvedRuntimeClasspath(project, false);
+	}
+
+	/**
+	 * Computes and returns the default unresolved runtime classpath for the given project.
+	 *
+	 * @param project
+	 *            the {@link IJavaProject} to compute the unresolved runtime classpath for
+	 * @param excludeTestCode
+	 *            if true, output folders corresponding to test sources and test dependencies are excluded
+	 * @return runtime classpath entries
+	 * @exception CoreException
+	 *                if unable to compute the runtime classpath
+	 * @see IRuntimeClasspathEntry
+	 * @since 3.10
+	 */
+	public static IRuntimeClasspathEntry[] computeUnresolvedRuntimeClasspath(IJavaProject project, boolean excludeTestCode) throws CoreException {
 		IClasspathEntry[] entries = project.getRawClasspath();
 		List<IRuntimeClasspathEntry> classpathEntries = new ArrayList<>(3);
 		for (int i = 0; i < entries.length; i++) {
@@ -898,12 +917,29 @@
 	 * @since 3.10
 	 */
 	public static IRuntimeClasspathEntry[] computeUnresolvedRuntimeDependencies(IJavaProject project) throws CoreException {
+		return computeUnresolvedRuntimeDependencies(project, false);
+	}
+
+	/**
+	 * Computes and returns the default unresolved runtime classpath and modulepath for the given project.
+	 *
+	 * @param project
+	 *            the {@link IJavaProject} to compute the unresolved runtime classpath and modulepath for
+	 * @param excludeTestCode
+	 *            if true, output folders corresponding to test sources and test dependencies are excluded
+	 * @return runtime classpath and modulepath entries
+	 * @exception CoreException
+	 *                if unable to compute the runtime classpath and/or modulepath
+	 * @see IRuntimeClasspathEntry
+	 * @since 3.10
+	 */
+	public static IRuntimeClasspathEntry[] computeUnresolvedRuntimeDependencies(IJavaProject project, boolean excludeTestCode) throws CoreException {
 		List<IRuntimeClasspathEntry> classpathEntries = new ArrayList<>(3);
 		if (!(project instanceof JavaProject)) {
 			return classpathEntries.toArray(new IRuntimeClasspathEntry[classpathEntries.size()]);
 		}
 		JavaProject javaProject = (JavaProject) project;
-		IClasspathEntry[] entries = javaProject.getExpandedClasspath();
+		IClasspathEntry[] entries = javaProject.getExpandedClasspath(excludeTestCode);
 
 		IClasspathEntry entry1 = JavaCore.newProjectEntry(project.getProject().getFullPath());
 		if (isModularProject(project)) {
@@ -1187,6 +1223,7 @@
 	 * @since 2.0
 	 */
 	public static IRuntimeClasspathEntry[] resolveRuntimeClasspathEntry(IRuntimeClasspathEntry entry, ILaunchConfiguration configuration) throws CoreException {
+		boolean excludeTestCode = configuration.getAttribute(IJavaLaunchConfigurationConstants.ATTR_EXCLUDE_TEST_CODE, false);
 		switch (entry.getType()) {
 			case IRuntimeClasspathEntry.PROJECT:
 				// if the project has multiple output locations, they must be returned
@@ -1197,7 +1234,7 @@
 					if (project == null || !p.isOpen() || !project.exists()) {
 						return new IRuntimeClasspathEntry[0];
 					}
-					IRuntimeClasspathEntry[] entries = resolveOutputLocations(project, entry.getClasspathProperty());
+					IRuntimeClasspathEntry[] entries = resolveOutputLocations(project, entry.getClasspathProperty(), excludeTestCode);
 					if (entries != null) {
 						return entries;
 					}
@@ -1211,7 +1248,7 @@
 			case IRuntimeClasspathEntry.VARIABLE:
 				IRuntimeClasspathEntryResolver resolver = getVariableResolver(entry.getVariableName());
 				if (resolver == null) {
-					IRuntimeClasspathEntry[] resolved = resolveVariableEntry(entry, null, configuration);
+					IRuntimeClasspathEntry[] resolved = resolveVariableEntry(entry, null, false, configuration);
 					if (resolved != null) {
 						return resolved;
 					}
@@ -1221,7 +1258,7 @@
 			case IRuntimeClasspathEntry.CONTAINER:
 				resolver = getContainerResolver(entry.getVariableName());
 				if (resolver == null) {
-					return computeDefaultContainerEntries(entry, configuration);
+					return computeDefaultContainerEntries(entry, configuration, excludeTestCode);
 				}
 				return resolver.resolveRuntimeClasspathEntry(entry, configuration);
 			case IRuntimeClasspathEntry.ARCHIVE:
@@ -1258,16 +1295,21 @@
 	}
 
 	/**
-	 * Default resolution for a classpath variable - resolve to an archive. Only
-	 * one of project/configuration can be non-null.
+	 * Default resolution for a classpath variable - resolve to an archive. Only one of project/configuration can be non-null.
 	 *
-	 * @param entry the {@link IRuntimeClasspathEntry} to try and resolve
-	 * @param project the project context or <code>null</code>
-	 * @param configuration configuration context or <code>null</code>
+	 * @param entry
+	 *            the {@link IRuntimeClasspathEntry} to try and resolve
+	 * @param project
+	 *            the project context or <code>null</code>
+	 * @param excludeTestCode
+	 *            if true, exclude test-code (only used if project is non-null)
+	 * @param configuration
+	 *            configuration context or <code>null</code>
 	 * @return IRuntimeClasspathEntry[]
-	 * @throws CoreException if a problem is encountered trying to resolve the given classpath entry
+	 * @throws CoreException
+	 *             if a problem is encountered trying to resolve the given classpath entry
 	 */
-	private static IRuntimeClasspathEntry[] resolveVariableEntry(IRuntimeClasspathEntry entry, IJavaProject project, ILaunchConfiguration configuration) throws CoreException {
+	private static IRuntimeClasspathEntry[] resolveVariableEntry(IRuntimeClasspathEntry entry, IJavaProject project, boolean excludeTestCode, ILaunchConfiguration configuration) throws CoreException {
 		// default resolution - an archive
 		IPath archPath = JavaCore.getClasspathVariable(entry.getVariableName());
 		if (archPath != null) {
@@ -1300,7 +1342,7 @@
 				IRuntimeClasspathEntry runtimeArchEntry = newRuntimeClasspathEntry(archEntry);
 				runtimeArchEntry.setClasspathProperty(entry.getClasspathProperty());
 				if (configuration == null) {
-					return resolveRuntimeClasspathEntry(runtimeArchEntry, project);
+					return resolveRuntimeClasspathEntry(runtimeArchEntry, project, excludeTestCode);
 				}
 				return resolveRuntimeClasspathEntry(runtimeArchEntry, configuration);
 			}
@@ -1309,17 +1351,23 @@
 	}
 
 	/**
-	 * Returns runtime classpath entries corresponding to the output locations
-	 * of the given project, or null if the project only uses the default
+	 * Returns runtime classpath entries corresponding to the output locations of the given project, or null if the project only uses the default
 	 * output location.
 	 *
-	 * @param project the {@link IJavaProject} to resolve the output locations for
-	 * @param classpathProperty the type of classpath entries to create
+	 * @param project
+	 *            the {@link IJavaProject} to resolve the output locations for
+	 * @param classpathProperty
+	 *            the type of classpath entries to create
+	 * @param excludeTestCode
+	 *            if true, output folders corresponding to test sources are excluded
+	 *
 	 * @return IRuntimeClasspathEntry[] or <code>null</code>
-	 * @throws CoreException if output resolution encounters a problem
+	 * @throws CoreException
+	 *             if output resolution encounters a problem
 	 */
-	private static IRuntimeClasspathEntry[] resolveOutputLocations(IJavaProject project, int classpathProperty) throws CoreException {
+	private static IRuntimeClasspathEntry[] resolveOutputLocations(IJavaProject project, int classpathProperty, boolean excludeTestCode) throws CoreException {
 		List<IPath> nonDefault = new ArrayList<>();
+		boolean defaultUsedByNonTest = false;
 		if (project.exists() && project.getProject().isOpen()) {
 			IClasspathEntry entries[] = project.getRawClasspath();
 			for (int i = 0; i < entries.length; i++) {
@@ -1327,20 +1375,28 @@
 				if (classpathEntry.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
 					IPath path = classpathEntry.getOutputLocation();
 					if (path != null) {
-						nonDefault.add(path);
+						if (!(excludeTestCode && classpathEntry.isTest())) {
+							nonDefault.add(path);
+						}
+					} else {
+						if (!classpathEntry.isTest()) {
+							defaultUsedByNonTest = true;
+						}
 					}
 				}
 			}
 		}
 		boolean isModular = project.getModuleDescription() != null;
-		if (nonDefault.isEmpty() && !isModular) {
+		if (nonDefault.isEmpty() && !isModular && !excludeTestCode) {
 			// return here only if non-modular, because patch-module might be needed otherwise
 			return null;
 		}
 		// add the default location if not already included
 		IPath def = project.getOutputLocation();
-		if (!nonDefault.contains(def)) {
-			nonDefault.add(def);
+		if (!excludeTestCode || defaultUsedByNonTest) {
+			if (!nonDefault.contains(def)) {
+				nonDefault.add(def);
+			}
 		}
 		IRuntimeClasspathEntry[] locations = new IRuntimeClasspathEntry[nonDefault.size()];
 		for (int i = 0; i < locations.length; i++) {
@@ -1383,6 +1439,34 @@
 	 * @since 2.0
 	 */
 	public static IRuntimeClasspathEntry[] resolveRuntimeClasspathEntry(IRuntimeClasspathEntry entry, IJavaProject project) throws CoreException {
+		return resolveRuntimeClasspathEntry(entry, project, false);
+	}
+
+	/**
+	 * Returns resolved entries for the given entry in the context of the given
+	 * Java project. If the entry is of kind
+	 * <code>VARIABLE</code> or <code>CONTAINER</code>, variable and container
+	 * resolvers are consulted. If the entry is of kind <code>PROJECT</code>,
+	 * and the associated Java project specifies non-default output locations,
+	 * the corresponding output locations are returned. Otherwise, the given
+	 * entry is returned.
+	 * <p>
+	 * If the given entry is a variable entry, and a resolver is not registered,
+	 * the entry itself is returned. If the given entry is a container, and a
+	 * resolver is not registered, resolved runtime classpath entries are calculated
+	 * from the associated container classpath entries, in the context of the
+	 * given project.
+	 * </p>
+	 * @param entry runtime classpath entry
+	 * @param project Java project context
+	 * @param excludeTestCode
+	 *            if true, output folders corresponding to test sources and test dependencies are excluded
+	 * @return resolved runtime classpath entry
+	 * @exception CoreException if unable to resolve
+	 * @see IRuntimeClasspathEntryResolver
+	 * @since 3.10
+	 */
+	public static IRuntimeClasspathEntry[] resolveRuntimeClasspathEntry(IRuntimeClasspathEntry entry, IJavaProject project, boolean excludeTestCode) throws CoreException {
 		switch (entry.getType()) {
 			case IRuntimeClasspathEntry.PROJECT:
 				// if the project has multiple output locations, they must be returned
@@ -1391,7 +1475,7 @@
 					IProject p = (IProject)resource;
 					IJavaProject jp = JavaCore.create(p);
 					if (jp != null && p.isOpen() && jp.exists()) {
-						IRuntimeClasspathEntry[] entries = resolveOutputLocations(jp, entry.getClasspathProperty());
+						IRuntimeClasspathEntry[] entries = resolveOutputLocations(jp, entry.getClasspathProperty(), excludeTestCode);
 						if (entries != null) {
 							return entries;
 						}
@@ -1403,22 +1487,22 @@
 			case IRuntimeClasspathEntry.VARIABLE:
 				IRuntimeClasspathEntryResolver resolver = getVariableResolver(entry.getVariableName());
 				if (resolver == null) {
-					IRuntimeClasspathEntry[] resolved = resolveVariableEntry(entry, project, null);
+					IRuntimeClasspathEntry[] resolved = resolveVariableEntry(entry, project, excludeTestCode, null);
 					if (resolved != null) {
 						return resolved;
 					}
 					break;
 				}
-				return resolver.resolveRuntimeClasspathEntry(entry, project);
+				return resolver.resolveRuntimeClasspathEntry(entry, project, excludeTestCode);
 			case IRuntimeClasspathEntry.CONTAINER:
 				resolver = getContainerResolver(entry.getVariableName());
 				if (resolver == null) {
-					return computeDefaultContainerEntries(entry, project);
+					return computeDefaultContainerEntries(entry, project, excludeTestCode);
 				}
-				return resolver.resolveRuntimeClasspathEntry(entry, project);
+				return resolver.resolveRuntimeClasspathEntry(entry, project, excludeTestCode);
 			case IRuntimeClasspathEntry.OTHER:
 				resolver = getContributedResolver(((IRuntimeClasspathEntry2)entry).getTypeId());
-				return resolver.resolveRuntimeClasspathEntry(entry, project);
+				return resolver.resolveRuntimeClasspathEntry(entry, project, excludeTestCode);
 			default:
 				break;
 		}
@@ -1426,30 +1510,40 @@
 	}
 
 	/**
-	 * Performs default resolution for a container entry.
-	 * Delegates to the Java model.
-	 * @param entry the {@link IRuntimeClasspathEntry} to compute default container entries for
-	 * @param config the backing {@link ILaunchConfiguration}
+	 * Performs default resolution for a container entry. Delegates to the Java model.
+	 *
+	 * @param entry
+	 *            the {@link IRuntimeClasspathEntry} to compute default container entries for
+	 * @param config
+	 *            the backing {@link ILaunchConfiguration}
+	 * @param excludeTestCode
+	 *            if true, output folders corresponding to test sources and test dependencies are excluded
 	 * @return the complete listing of default container entries or an empty list, never <code>null</code>
-	 * @throws CoreException if the computation encounters a problem
+	 * @throws CoreException
+	 *             if the computation encounters a problem
 	 */
-	private static IRuntimeClasspathEntry[] computeDefaultContainerEntries(IRuntimeClasspathEntry entry, ILaunchConfiguration config) throws CoreException {
+	private static IRuntimeClasspathEntry[] computeDefaultContainerEntries(IRuntimeClasspathEntry entry, ILaunchConfiguration config, boolean excludeTestCode) throws CoreException {
 		IJavaProject project = entry.getJavaProject();
 		if (project == null) {
 			project = getJavaProject(config);
 		}
-		return computeDefaultContainerEntries(entry, project);
+		return computeDefaultContainerEntries(entry, project, excludeTestCode);
 	}
 
 	/**
-	 * Performs default resolution for a container entry.
-	 * Delegates to the Java model.
-	 * @param entry the {@link IRuntimeClasspathEntry} to compute default container entries for
-	 * @param project the backing {@link IJavaProject}
+	 * Performs default resolution for a container entry. Delegates to the Java model.
+	 *
+	 * @param entry
+	 *            the {@link IRuntimeClasspathEntry} to compute default container entries for
+	 * @param project
+	 *            the backing {@link IJavaProject}
+	 * @param excludeTestCode
+	 *            if true, output folders corresponding to test sources and test dependencies are excluded
 	 * @return the complete listing of default container entries or an empty list, never <code>null</code>
-	 * @throws CoreException if the computation encounters a problem
+	 * @throws CoreException
+	 *             if the computation encounters a problem
 	 */
-	private static IRuntimeClasspathEntry[] computeDefaultContainerEntries(IRuntimeClasspathEntry entry, IJavaProject project) throws CoreException {
+	private static IRuntimeClasspathEntry[] computeDefaultContainerEntries(IRuntimeClasspathEntry entry, IJavaProject project, boolean excludeTestCode) throws CoreException {
 		if (project == null || entry == null) {
 			// cannot resolve without entry or project context
 			return new IRuntimeClasspathEntry[0];
@@ -1493,7 +1587,7 @@
 					if (!projects.contains(jp)) {
 						projects.add(jp);
 						IRuntimeClasspathEntry classpath = newDefaultProjectClasspathEntry(jp);
-						IRuntimeClasspathEntry[] entries = resolveRuntimeClasspathEntry(classpath, jp);
+						IRuntimeClasspathEntry[] entries = resolveRuntimeClasspathEntry(classpath, jp, excludeTestCode);
 						for (int j = 0; j < entries.length; j++) {
 							IRuntimeClasspathEntry e = entries[j];
 							if (!resolved.contains(e)) {
diff --git a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/StandardClasspathProvider.java b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/StandardClasspathProvider.java
index 396ee7f..715b639 100644
--- a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/StandardClasspathProvider.java
+++ b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/StandardClasspathProvider.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2017 IBM Corporation and others.
+ * Copyright (c) 2000, 2018 IBM Corporation and others.
  * 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
@@ -38,6 +38,7 @@
 	public IRuntimeClasspathEntry[] computeUnresolvedClasspath(ILaunchConfiguration configuration) throws CoreException {
 		boolean useDefault = configuration.getAttribute(IJavaLaunchConfigurationConstants.ATTR_DEFAULT_CLASSPATH, true);
 		boolean isModular = JavaRuntime.isModularConfiguration(configuration);
+		boolean excludeTestCode = configuration.getAttribute(IJavaLaunchConfigurationConstants.ATTR_EXCLUDE_TEST_CODE, false);
 		if (useDefault) {
 			IJavaProject proj = JavaRuntime.getJavaProject(configuration);
 			IRuntimeClasspathEntry jreEntry = JavaRuntime.computeJREEntry(configuration);
@@ -50,9 +51,9 @@
 			}
 			IRuntimeClasspathEntry[] entries = null;
 			if (isModular) {
-				entries = JavaRuntime.computeUnresolvedRuntimeDependencies(proj);
+				entries = JavaRuntime.computeUnresolvedRuntimeDependencies(proj, excludeTestCode);
 			} else {
-				entries = JavaRuntime.computeUnresolvedRuntimeClasspath(proj);
+				entries = JavaRuntime.computeUnresolvedRuntimeClasspath(proj, excludeTestCode);
 			}
 			// replace project JRE with config's JRE
 			IRuntimeClasspathEntry projEntry = isModular ? JavaRuntime.computeModularJREEntry(proj) : JavaRuntime.computeJREEntry(proj);