1 /* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Eclipse Public License, Version 1.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.eclipse.org/org/documents/epl-v10.php 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.ide.eclipse.adt.internal.wizards.templates; 17 18 import static com.android.SdkConstants.CURRENT_PLATFORM; 19 import static com.android.SdkConstants.FD_TOOLS; 20 import static com.android.SdkConstants.PLATFORM_WINDOWS; 21 import static com.android.ide.eclipse.adt.internal.wizards.templates.NewProjectWizard.ATTR_MIN_API; 22 import static com.android.ide.eclipse.adt.internal.wizards.templates.NewProjectWizard.ATTR_MIN_BUILD_API; 23 import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.ATTR_ID; 24 25 import com.android.annotations.NonNull; 26 import com.android.annotations.Nullable; 27 import com.android.sdklib.SdkVersionInfo; 28 import com.android.ide.eclipse.adt.AdtPlugin; 29 import com.android.ide.eclipse.adt.AdtUtils; 30 import com.android.ide.eclipse.adt.internal.lint.EclipseLintClient; 31 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs; 32 import com.android.ide.eclipse.adt.internal.sdk.Sdk; 33 import com.android.ide.eclipse.tests.SdkLoadingTestCase; 34 import com.android.sdklib.IAndroidTarget; 35 import com.android.utils.GrabProcessOutput; 36 import com.android.utils.GrabProcessOutput.IProcessOutput; 37 import com.android.utils.GrabProcessOutput.Wait; 38 import com.android.tools.lint.checks.ManifestDetector; 39 import com.android.tools.lint.checks.SecurityDetector; 40 import com.android.tools.lint.client.api.Configuration; 41 import com.android.tools.lint.client.api.DefaultConfiguration; 42 import com.android.tools.lint.client.api.JavaParser; 43 import com.android.tools.lint.client.api.LintClient; 44 import com.android.tools.lint.client.api.LintDriver; 45 import com.android.tools.lint.client.api.XmlParser; 46 import com.android.tools.lint.detector.api.Category; 47 import com.android.tools.lint.detector.api.Context; 48 import com.android.tools.lint.detector.api.Issue; 49 import com.android.tools.lint.detector.api.Location; 50 import com.android.tools.lint.detector.api.Project; 51 import com.android.tools.lint.detector.api.Scope; 52 import com.android.tools.lint.detector.api.Severity; 53 import com.android.tools.lint.detector.api.TextFormat; 54 import com.google.common.base.Charsets; 55 import com.google.common.base.Stopwatch; 56 import com.google.common.collect.Lists; 57 import com.google.common.collect.Sets; 58 import com.google.common.io.Files; 59 60 import org.eclipse.core.resources.IProject; 61 import org.eclipse.core.runtime.CoreException; 62 import org.eclipse.core.runtime.IPath; 63 import org.eclipse.core.runtime.IProgressMonitor; 64 import org.eclipse.core.runtime.IStatus; 65 import org.eclipse.core.runtime.NullProgressMonitor; 66 import org.eclipse.core.runtime.Platform; 67 import org.eclipse.core.runtime.QualifiedName; 68 import org.eclipse.core.runtime.Status; 69 import org.eclipse.core.runtime.jobs.Job; 70 import org.eclipse.ltk.core.refactoring.Change; 71 import org.eclipse.ltk.core.refactoring.CompositeChange; 72 import org.w3c.dom.Element; 73 74 import java.io.File; 75 import java.io.IOException; 76 import java.lang.reflect.InvocationTargetException; 77 import java.util.ArrayList; 78 import java.util.Collections; 79 import java.util.List; 80 import java.util.Set; 81 82 /** 83 * Unit tests for template instantiation. 84 * <p> 85 * Note: This test can take multiple hours to run! 86 * 87 * <p> 88 * TODO: Test all permutations of variables (it currently just varies one at a time with the 89 * rest of the defaults) 90 * TODO: Test trying to change strings arguments (currently just varies enums and booleans) 91 * TODO: Test adding multiple instances of the templates (to look for resource conflicts) 92 */ 93 @SuppressWarnings("javadoc") 94 public class TemplateHandlerTest extends SdkLoadingTestCase { 95 /** 96 * Flag used to quickly check each template once (for one version), to get 97 * quicker feedback on whether something is broken instead of waiting for 98 * all the versions for each template first 99 */ 100 private static final boolean TEST_FEWER_API_VERSIONS = true; 101 private static final boolean TEST_JUST_ONE_MIN_SDK = false; 102 private static final boolean TEST_JUST_ONE_BUILD_TARGET = true; 103 private static final boolean TEST_JUST_ONE_TARGET_SDK_VERSION = true; 104 private QualifiedName ERROR_KEY = new QualifiedName(AdtPlugin.PLUGIN_ID, "JobErrorKey"); 105 private static int sCount = 0; 106 /** 107 * If true, check this template with all the interesting ( 108 * {@link #isInterestingApiLevel(int)}) api versions 109 */ 110 private boolean mApiSensitiveTemplate; 111 /** 112 * Set of templates already tested with separate unit test; remainder is 113 * checked in {@link #testCreateRemainingProjects()} 114 */ 115 private static final Set<File> sProjectTestedSeparately = Sets.newHashSet(); 116 /** 117 * Set of templates already tested with separate unit test; remainder is 118 * checked in {@link #testCreateRemainingTemplates()} 119 */ 120 private static final Set<File> sTemplateTestedSeparately = Sets.newHashSet(); 121 122 @Override setUp()123 protected void setUp() throws Exception { 124 super.setUp(); 125 mApiSensitiveTemplate = true; 126 } 127 128 /** 129 * Is the given api level interesting for testing purposes? This is used to 130 * skip gaps, such that we for example only check say api 8, 9, 11, 14, etc 131 * -- versions where the <b>templates</b> are doing conditional changes. To 132 * be EXTRA comprehensive, occasionally try returning true unconditionally 133 * here to test absolutely everything. 134 */ isInterestingApiLevel(int api)135 private boolean isInterestingApiLevel(int api) { 136 // For templates that aren't API sensitive, only test with API = 16 137 if (!mApiSensitiveTemplate) { 138 return api == 16; 139 } 140 141 switch (api) { 142 case 1: 143 case 8: 144 return true; 145 case 11: 146 return true; 147 case 14: 148 return true; 149 case 9: 150 case 16: 151 return !TEST_FEWER_API_VERSIONS; 152 default: 153 return false; 154 } 155 } 156 testNewBlankProject()157 public void testNewBlankProject() throws Exception { 158 Stopwatch stopwatch = Stopwatch.createUnstarted(); 159 stopwatch.start(); 160 checkProjectWithActivity(null); 161 stopwatch.stop(); 162 System.out.println("Checked blank project successfully in " 163 + stopwatch.toString()); 164 } 165 testNewBlankActivity()166 public void testNewBlankActivity() throws Exception { 167 checkCreateTemplate("activities", "BlankActivity"); 168 } 169 testBlankActivityInProject()170 public void testBlankActivityInProject() throws Exception { 171 checkCreateActivityInProject("BlankActivity"); 172 } 173 testNewMasterDetailFlow()174 public void testNewMasterDetailFlow() throws Exception { 175 checkCreateTemplate("activities", "MasterDetailFlow"); 176 } 177 testMasterDetailFlowInProject()178 public void testMasterDetailFlowInProject() throws Exception { 179 checkCreateActivityInProject("MasterDetailFlow"); 180 } 181 testNewFullscreen()182 public void testNewFullscreen() throws Exception { 183 checkCreateTemplate("activities", "FullscreenActivity"); 184 } 185 testFullscreenInProject()186 public void testFullscreenInProject() throws Exception { 187 checkCreateActivityInProject("FullscreenActivity"); 188 } 189 testNewLoginActivity()190 public void testNewLoginActivity() throws Exception { 191 checkCreateTemplate("activities", "LoginActivity"); 192 } 193 testLoginActivityInProject()194 public void testLoginActivityInProject() throws Exception { 195 checkCreateActivityInProject("MasterDetailFlow"); 196 } 197 testNewSettingsActivity()198 public void testNewSettingsActivity() throws Exception { 199 checkCreateTemplate("activities", "SettingsActivity"); 200 } 201 testSettingsActivityInProject()202 public void testSettingsActivityInProject() throws Exception { 203 checkCreateActivityInProject("SettingsActivity"); 204 } 205 testNewBroadcastReceiver()206 public void testNewBroadcastReceiver() throws Exception { 207 // No need to try this template with multiple platforms, one is adequate 208 mApiSensitiveTemplate = false; 209 checkCreateTemplate("other", "BroadcastReceiver"); 210 } 211 testNewContentProvider()212 public void testNewContentProvider() throws Exception { 213 mApiSensitiveTemplate = false; 214 checkCreateTemplate("other", "ContentProvider"); 215 } 216 testNewCustomView()217 public void testNewCustomView() throws Exception { 218 mApiSensitiveTemplate = false; 219 checkCreateTemplate("other", "CustomView"); 220 } 221 testNewService()222 public void testNewService() throws Exception { 223 mApiSensitiveTemplate = false; 224 checkCreateTemplate("other", "Service"); 225 } 226 testCreateRemainingTemplates()227 public void testCreateRemainingTemplates() throws Exception { 228 sCount = 0; 229 long begin = System.currentTimeMillis(); 230 TemplateManager manager = new TemplateManager(); 231 List<File> other = manager.getTemplates("other"); 232 for (File templateFile : other) { 233 if (sTemplateTestedSeparately.contains(templateFile)) { 234 continue; 235 } 236 checkTemplate(templateFile); 237 } 238 // Also try creating templates, not as part of creating a project 239 List<File> activities = manager.getTemplates("activities"); 240 for (File templateFile : activities) { 241 if (sTemplateTestedSeparately.contains(templateFile)) { 242 continue; 243 } 244 checkTemplate(templateFile); 245 } 246 long end = System.currentTimeMillis(); 247 System.out.println("Successfully checked " + sCount + " template permutations in " 248 + ((end - begin) / (1000 * 60)) + " minutes"); 249 } 250 testCreateRemainingProjects()251 public void testCreateRemainingProjects() throws Exception { 252 sCount = 0; 253 long begin = System.currentTimeMillis(); 254 TemplateManager manager = new TemplateManager(); 255 List<File> templates = manager.getTemplates("activities"); 256 for (File activityFile : templates) { 257 if (sTemplateTestedSeparately.contains(activityFile)) { 258 continue; 259 } 260 checkProjectWithActivity(activityFile.getName()); 261 } 262 long end = System.currentTimeMillis(); 263 System.out.println("Successfully checked " + sCount + " project permutations in " 264 + ((end - begin) / (1000 * 60)) + " minutes"); 265 } 266 267 // ---- Test support code below ---- 268 checkCreateActivityInProject(String activityName)269 private void checkCreateActivityInProject(String activityName) throws Exception { 270 Stopwatch stopwatch = Stopwatch.createUnstarted(); 271 stopwatch.start(); 272 File templateFile = findTemplate("activities", activityName); 273 sProjectTestedSeparately.add(templateFile); 274 checkProjectWithActivity(templateFile.getName()); 275 stopwatch.stop(); 276 System.out.println("Checked " + templateFile.getName() + " successfully in " 277 + stopwatch.toString()); 278 } 279 checkCreateTemplate(String category, String name)280 private void checkCreateTemplate(String category, String name) throws Exception { 281 Stopwatch stopwatch = Stopwatch.createUnstarted(); 282 stopwatch.start(); 283 File templateFile = findTemplate(category, name); 284 assertNotNull(templateFile); 285 sTemplateTestedSeparately.add(templateFile); 286 checkTemplate(templateFile); 287 stopwatch.stop(); 288 System.out.println("Checked " + templateFile.getName() + " successfully in " 289 + stopwatch.toString()); 290 } 291 findTemplate(String category, String name)292 private static File findTemplate(String category, String name) { 293 File templateRootFolder = TemplateManager.getTemplateRootFolder(); 294 assertNotNull(templateRootFolder); 295 File file = new File(templateRootFolder, category + File.separator + name); 296 assertTrue(file.getPath(), file.exists()); 297 return file; 298 } 299 checkTemplate(File templateFile)300 private void checkTemplate(File templateFile) throws Exception { 301 NewProjectWizardState values = new NewProjectWizardState(); 302 values.applicationName = "My Application"; 303 values.packageName = "my.pkg2"; 304 305 values.isLibrary = false; 306 values.createIcon = false; 307 values.useDefaultLocation = true; 308 values.createActivity = false; 309 310 String projectNameBase = "MyTemplateProject_" + templateFile.getName(); 311 values.projectName = projectNameBase; 312 values.createActivity = false; 313 314 // Create the new template 315 316 NewTemplateWizardState state = new NewTemplateWizardState(); 317 state.setTemplateLocation(templateFile); 318 state.minSdkLevel = values.minSdkLevel; 319 320 // Iterate over all (valid) combinations of build target, minSdk and targetSdk 321 IAndroidTarget[] targets = Sdk.getCurrent().getTargets(); 322 for (int i = targets.length - 1; i >= 0; i--) { 323 IAndroidTarget target = targets[i]; 324 if (!target.isPlatform()) { 325 continue; 326 } 327 if (!isInterestingApiLevel(target.getVersion().getApiLevel())) { 328 continue; 329 } 330 331 for (int minSdk = 1; 332 minSdk <= SdkVersionInfo.HIGHEST_KNOWN_API; 333 minSdk++) { 334 // Don't bother checking *every* single minSdk, just pick some interesting ones 335 if (!isInterestingApiLevel(minSdk)) { 336 continue; 337 } 338 339 for (int targetSdk = minSdk; 340 targetSdk <= SdkVersionInfo.HIGHEST_KNOWN_API; 341 targetSdk++) { 342 if (!isInterestingApiLevel(targetSdk)) { 343 continue; 344 } 345 346 // Make sure this template is supported with these versions 347 IStatus status = values.template.validateTemplate( 348 minSdk, target.getVersion().getApiLevel()); 349 if (status != null && !status.isOK()) { 350 continue; 351 } 352 353 // Also make sure activity is enabled for these versions 354 status = state.getTemplateHandler().validateTemplate( 355 minSdk, target.getVersion().getApiLevel()); 356 if (status != null && !status.isOK()) { 357 continue; 358 } 359 360 // Iterate over all new new project templates 361 362 // should I try all options of theme with all platforms? 363 // or just try all platforms, with one setting for each? 364 // doesn't seem like I need to multiply 365 // just pick the best setting that applies instead for each platform 366 List<Parameter> parameters = values.template.getTemplate().getParameters(); 367 projectParameters: 368 for (Parameter parameter : parameters) { 369 List<Element> options = parameter.getOptions(); 370 if (parameter.type == Parameter.Type.ENUM) { 371 for (Element element : options) { 372 Option option = Option.get(element); 373 String optionId = option.id; 374 int optionMinSdk = option.minSdk; 375 int optionMinBuildApi = option.minBuild; 376 if (optionMinSdk <= minSdk && 377 optionMinBuildApi <= target.getVersion().getApiLevel()) { 378 values.parameters.put(parameter.id, optionId); 379 if (parameter.id.equals("baseTheme")) { 380 String base = projectNameBase + "_min_" + minSdk 381 + "_target_" + targetSdk 382 + "_build_" + target.getVersion().getApiLevel() 383 + "_theme_" + optionId; 384 System.out.println("checking base " + base); 385 386 checkApiTarget(minSdk, targetSdk, target, values, base, 387 state); 388 break projectParameters; 389 } 390 } 391 } 392 } 393 } 394 395 if (TEST_JUST_ONE_TARGET_SDK_VERSION) { 396 break; 397 } 398 } 399 400 if (TEST_JUST_ONE_MIN_SDK) { 401 break; 402 } 403 } 404 405 if (TEST_JUST_ONE_BUILD_TARGET) { 406 break; 407 } 408 } 409 } 410 checkProjectWithActivity(String activity)411 private void checkProjectWithActivity(String activity) throws Exception { 412 NewProjectWizardState values = new NewProjectWizardState(); 413 values.applicationName = "My Application"; 414 values.packageName = "my.pkg"; 415 416 values.isLibrary = false; 417 values.createIcon = false; 418 values.useDefaultLocation = true; 419 420 // These are basically unused; passed as defaults 421 values.activityName = activity == null ? "Blank" : activity; 422 values.activityTitle = "My Activity Title"; 423 424 String projectNameBase = "MyProject_" + values.activityName; 425 values.projectName = projectNameBase; 426 427 values.createActivity = activity != null; 428 NewTemplateWizardState activityValues = values.activityValues; 429 assertNotNull(activityValues); 430 activityValues.minSdkLevel = values.minSdkLevel; 431 432 433 // Iterate over all (valid) combinations of build target, minSdk and targetSdk 434 IAndroidTarget[] targets = Sdk.getCurrent().getTargets(); 435 for (int i = targets.length - 1; i >= 0; i--) { 436 IAndroidTarget target = targets[i]; 437 if (!target.isPlatform()) { 438 continue; 439 } 440 if (!isInterestingApiLevel(target.getVersion().getApiLevel())) { 441 continue; 442 } 443 444 for (int minSdk = 1; 445 minSdk <= SdkVersionInfo.HIGHEST_KNOWN_API; 446 minSdk++) { 447 // Don't bother checking *every* single minSdk, just pick some interesting ones 448 if (!isInterestingApiLevel(minSdk)) { 449 continue; 450 } 451 452 for (int targetSdk = minSdk; 453 targetSdk <= SdkVersionInfo.HIGHEST_KNOWN_API; 454 targetSdk++) { 455 if (!isInterestingApiLevel(targetSdk)) { 456 continue; 457 } 458 459 // Make sure this template is supported with these versions 460 IStatus status = values.template.validateTemplate( 461 values.minSdkLevel, values.getBuildApi()); 462 if (status != null && !status.isOK()) { 463 continue; 464 } 465 466 // Also make sure activity is enabled for these versions 467 status = values.activityValues.getTemplateHandler().validateTemplate( 468 values.minSdkLevel, values.getBuildApi()); 469 if (status != null && !status.isOK()) { 470 continue; 471 } 472 473 // Iterate over all new new project templates 474 475 // should I try all options of theme with all platforms? 476 // or just try all platforms, with one setting for each? 477 // doesn't seem like I need to multiply 478 // just pick the best setting that applies instead for each platform 479 List<Parameter> parameters = values.template.getTemplate().getParameters(); 480 for (Parameter parameter : parameters) { 481 List<Element> options = parameter.getOptions(); 482 if (parameter.type == Parameter.Type.ENUM) { 483 for (Element element : options) { 484 Option option = Option.get(element); 485 String optionId = option.id; 486 int optionMinSdk = option.minSdk; 487 int optionMinBuildApi = option.minBuild; 488 if (optionMinSdk <= minSdk && 489 optionMinBuildApi <= target.getVersion().getApiLevel()) { 490 values.parameters.put(parameter.id, optionId); 491 if (parameter.id.equals("baseTheme")) { 492 String base = projectNameBase + "_min_" + minSdk 493 + "_target_" + targetSdk 494 + "_build_" + target.getVersion().getApiLevel() 495 + "_theme_" + optionId; 496 System.out.println("checking base " + base); 497 498 checkApiTarget(minSdk, targetSdk, target, values, base, 499 null); 500 501 } 502 } 503 } 504 } 505 } 506 507 if (TEST_JUST_ONE_TARGET_SDK_VERSION) { 508 break; 509 } 510 } 511 512 if (TEST_JUST_ONE_MIN_SDK) { 513 break; 514 } 515 } 516 517 if (TEST_JUST_ONE_BUILD_TARGET) { 518 break; 519 } 520 } 521 } 522 checkApiTarget( int minSdk, int targetSdk, @NonNull IAndroidTarget target, @NonNull NewProjectWizardState projectValues, @NonNull String projectNameBase, @Nullable NewTemplateWizardState templateValues)523 private void checkApiTarget( 524 int minSdk, 525 int targetSdk, 526 @NonNull IAndroidTarget target, 527 @NonNull NewProjectWizardState projectValues, 528 @NonNull String projectNameBase, 529 @Nullable NewTemplateWizardState templateValues) 530 throws Exception { 531 NewTemplateWizardState values = 532 projectValues.createActivity ? projectValues.activityValues : templateValues; 533 534 projectValues.minSdk = Integer.toString(minSdk); 535 projectValues.minSdkLevel = minSdk; 536 projectValues.targetSdkLevel = targetSdk; 537 projectValues.target = target; 538 539 if (values == null) { 540 checkProject(projectValues, templateValues); 541 return; 542 } 543 544 // Next check all other parameters, cycling through booleans and enums. 545 TemplateHandler templateHandler = values.getTemplateHandler(); 546 TemplateMetadata template = templateHandler.getTemplate(); 547 assertNotNull(template); 548 List<Parameter> parameters = template.getParameters(); 549 550 if (!projectValues.createActivity) { 551 for (Parameter parameter : parameters) { 552 values.parameters.put(parameter.id, parameter.value); 553 } 554 } 555 556 for (Parameter parameter : parameters) { 557 if (parameter.type == Parameter.Type.SEPARATOR 558 || parameter.type == Parameter.Type.STRING) { 559 // TODO: Consider whether we should attempt some strings here 560 continue; 561 } 562 563 // The initial (default value); revert to this one after cycling, 564 Object initial = values.parameters.get(parameter.id); 565 566 if (parameter.type == Parameter.Type.ENUM) { 567 List<Element> options = parameter.getOptions(); 568 for (Element element : options) { 569 Option option = Option.get(element); 570 String optionId = option.id; 571 int optionMinSdk = option.minSdk; 572 int optionMinBuildApi = option.minBuild; 573 if (projectValues.minSdkLevel >= optionMinSdk && 574 projectValues.getBuildApi() >= optionMinBuildApi) { 575 values.parameters.put(parameter.id, optionId); 576 projectValues.projectName = projectNameBase + "_" + parameter.id 577 + "_" + optionId; 578 checkProject(projectValues, templateValues); 579 } 580 } 581 } else { 582 assert parameter.type == Parameter.Type.BOOLEAN; 583 if (parameter.id.equals("isLauncher") && projectValues.createActivity) { 584 // Skipping this one: always true when launched from new project 585 continue; 586 } 587 boolean value = false; 588 values.parameters.put(parameter.id, value); 589 projectValues.projectName = projectNameBase + "_" + parameter.id 590 + "_" + value; 591 checkProject(projectValues, templateValues); 592 593 value = true; 594 values.parameters.put(parameter.id, value); 595 projectValues.projectName = projectNameBase + "_" + parameter.id 596 + "_" + value; 597 checkProject(projectValues, templateValues); 598 } 599 600 values.parameters.put(parameter.id, initial); 601 } 602 } 603 604 private final class OutputGrabber implements IProcessOutput { 605 private final List<String> output = Lists.newArrayList(); 606 private final List<String> error = Lists.newArrayList(); 607 608 @Override out(@ullable String line)609 public void out(@Nullable String line) { 610 if (line != null) { 611 output.add(line); 612 } 613 } 614 615 @Override err(@ullable String line)616 public void err(@Nullable String line) { 617 if (line != null) { 618 error.add(line); 619 } 620 } 621 622 @NonNull getOutput()623 private List<String> getOutput() { 624 return output; 625 } 626 627 @NonNull getError()628 private List<String> getError() { 629 return error; 630 } 631 } 632 633 private static class Option { 634 private String id; 635 private int minSdk; 636 private int minBuild; 637 Option(String id, int minSdk, int minBuild)638 public Option(String id, int minSdk, int minBuild) { 639 this.id = id; 640 this.minSdk = minSdk; 641 this.minBuild = minBuild; 642 } 643 get(Element option)644 private static Option get(Element option) { 645 String optionId = option.getAttribute(ATTR_ID); 646 String minApiString = option.getAttribute(ATTR_MIN_API); 647 int optionMinSdk = 1; 648 if (minApiString != null && !minApiString.isEmpty()) { 649 try { 650 optionMinSdk = Integer.parseInt(minApiString); 651 } catch (NumberFormatException nufe) { 652 // Templates aren't allowed to contain codenames, should 653 // always be an integer 654 AdtPlugin.log(nufe, null); 655 optionMinSdk = 1; 656 } 657 } 658 String minBuildApiString = option.getAttribute(ATTR_MIN_BUILD_API); 659 int optionMinBuildApi = 1; 660 if (minBuildApiString != null && !minBuildApiString.isEmpty()) { 661 try { 662 optionMinBuildApi = Integer.parseInt(minBuildApiString); 663 } catch (NumberFormatException nufe) { 664 // Templates aren't allowed to contain codenames, should 665 // always be an integer 666 AdtPlugin.log(nufe, null); 667 optionMinBuildApi = 1; 668 } 669 } 670 671 672 return new Option(optionId, optionMinSdk, optionMinBuildApi); 673 } 674 } 675 checkProject( @onNull NewProjectWizardState projectValues, @Nullable NewTemplateWizardState templateValues)676 private void checkProject( 677 @NonNull NewProjectWizardState projectValues, 678 @Nullable NewTemplateWizardState templateValues) throws Exception { 679 NewTemplateWizardState values = 680 projectValues.createActivity ? projectValues.activityValues : templateValues; 681 if (values != null) { // if not, creating blank project 682 // Validate that a template is only being used in a context it is compatible with! 683 IStatus status = values.getTemplateHandler().validateTemplate( 684 projectValues.minSdkLevel, projectValues.getBuildApi()); 685 if (status != null && !status.isOK()) { 686 fail(status.toString()); 687 } 688 } 689 690 assertNotNull(projectValues.projectName); 691 projectValues.projectName = AdtUtils.getUniqueProjectName(projectValues.projectName, ""); 692 IPath workspace = Platform.getLocation(); 693 String projectLocation = workspace.append(projectValues.projectName).toOSString(); 694 projectValues.projectLocation = projectLocation; 695 696 // Create project with the given parameter map 697 final IProject project = createProject(projectValues); 698 assertNotNull(project); 699 700 if (templateValues != null) { 701 templateValues.project = project; 702 List<Change> changes = templateValues.computeChanges(); 703 if (!changes.isEmpty()) { 704 try { 705 CompositeChange composite = new CompositeChange("", 706 changes.toArray(new Change[changes.size()])); 707 composite.perform(new NullProgressMonitor()); 708 } catch (CoreException e) { 709 fail(e.getLocalizedMessage()); 710 } 711 } 712 } 713 714 // Project creation has some async hooks so don't attempt to build it *right* away 715 Job job = new Job("Validate project") { 716 @Override 717 protected IStatus run(IProgressMonitor monitor) { 718 try { 719 ensureValidProject(this, project); 720 return Status.OK_STATUS; 721 } catch (Exception e) { 722 fail(e.toString()); 723 } 724 return null; 725 } 726 }; 727 job.schedule(1000); 728 job.join(); 729 Object property = job.getProperty(ERROR_KEY); 730 assertNull(property); 731 } 732 createProject(NewProjectWizardState values)733 private IProject createProject(NewProjectWizardState values) throws InvocationTargetException { 734 NewProjectWizard wizard = new NewProjectWizard(); 735 wizard.setValues(values); 736 wizard.performFinish(new NullProgressMonitor()); 737 738 if (TemplateHandler.sMostRecentException != null) { 739 fail(values.projectName + ": " + TemplateHandler.sMostRecentException.toString()); 740 } 741 742 IProject project = wizard.getProject(); 743 assertNotNull(project); 744 assertTrue(project.exists()); 745 System.out.println("Created project " + project + " : " + AdtUtils.getAbsolutePath(project)); 746 return project; 747 } 748 ensureValidProject(@onNull Job job, @NonNull IProject project)749 private void ensureValidProject(@NonNull Job job, @NonNull IProject project) throws Exception { 750 System.out.println("Begin build error check"); 751 ensureNoBuildErrors(job, project); 752 System.out.println("Finished build error check"); 753 754 System.out.println("Begin lint check"); 755 ensureNoLintWarnings(job, project); 756 System.out.println("Finished lint check"); 757 758 sCount++; 759 } 760 ensureNoLintWarnings(final Job job, IProject project)761 private void ensureNoLintWarnings(final Job job, IProject project) { 762 System.setProperty("com.android.tools.lint.bindir", AdtPrefs.getPrefs().getOsSdkFolder() 763 + File.separator + FD_TOOLS); 764 765 LintDriver driver = new LintDriver(EclipseLintClient.getRegistry(), new LintClient() { 766 @Override 767 public void report(@NonNull Context context, 768 @NonNull Issue issue, @NonNull Severity severity, 769 @Nullable Location location, @NonNull String message, @NonNull TextFormat format) { 770 String s = "Found lint error: " + issue.getId() + ": " + message + " at " + location; 771 job.setProperty(ERROR_KEY, s); 772 fail(s); 773 } 774 775 @Override 776 public Configuration getConfiguration(@NonNull Project p) { 777 return new DefaultConfiguration(this, p, null, new File("dummy.xml")) { 778 @Override 779 public boolean isEnabled(@NonNull Issue issue) { 780 // Doesn't work: hangs in unit test context, something about 781 // loading native libs. 782 if (issue.getCategory() == Category.ICONS){ 783 return false; 784 } 785 786 if (issue == ManifestDetector.TARGET_NEWER) { 787 // Don't complain about targetSdk < latest: we're deliberately 788 // testing that (to make sure templates compile etc in compat 789 // mode) 790 return false; 791 } 792 793 if (issue == SecurityDetector.EXPORTED_SERVICE 794 || issue == SecurityDetector.EXPORTED_PROVIDER 795 || issue == SecurityDetector.EXPORTED_RECEIVER) { 796 // Don't complain about missing permissions when exporting: the 797 // unit test is deliberately turning on exported 798 return false; 799 } 800 801 return true; 802 } 803 }; 804 } 805 806 @Override 807 @NonNull 808 public String readFile(@NonNull File file) { 809 try { 810 return Files.toString(file, Charsets.UTF_8); 811 } catch (IOException e) { 812 fail(e.toString() + " for " + file.getPath()); 813 return ""; 814 } 815 } 816 817 @Override 818 public void log(@NonNull Severity severity, @Nullable Throwable exception, 819 @Nullable String format, @Nullable Object... args) { 820 if (exception != null) { 821 exception.printStackTrace(); 822 } 823 if (format != null) { 824 if (args != null) { 825 System.err.println("Log: " + String.format(format, args)); 826 } else { 827 System.err.println("Unexpected log message " + format); 828 } 829 } 830 } 831 832 @Override 833 @Nullable 834 public JavaParser getJavaParser(@Nullable Project project) { 835 return new EclipseLintClient(null, null, null, false).getJavaParser(project); 836 } 837 838 @Override 839 public XmlParser getXmlParser() { 840 return new EclipseLintClient(null, null, null, false).getXmlParser(); 841 } 842 }); 843 File projectDir = AdtUtils.getAbsolutePath(project).toFile(); 844 assertNotNull(projectDir); 845 assertTrue(projectDir.getPath(), projectDir.isDirectory()); 846 driver.analyze(Collections.singletonList(projectDir), Scope.ALL); 847 } 848 849 // Wait for test build support. 850 // This is copied from {@link SampleProjectTest} 851 ensureNoBuildErrors(final Job job, final IProject project)852 private void ensureNoBuildErrors(final Job job, final IProject project) throws Exception { 853 File projectDir = AdtUtils.getAbsolutePath(project).toFile(); 854 855 // Checking the build in Eclipse doesn't work well, because of asynchronous issues 856 // (it looks like not all necessary changes are applied, and even adding waits works 857 // unpredictably.) 858 // 859 // So instead we do it via the command line. 860 // First add ant support: 861 // $ android update project -p . 862 // Then we run ant and look at the exit code to make sure it worked. 863 864 List<String> command = new ArrayList<String>(); 865 command.add(AdtPlugin.getOsSdkToolsFolder() + "android" + 866 (CURRENT_PLATFORM == PLATFORM_WINDOWS ? ".bat" : "")); 867 command.add("update"); 868 command.add("project"); 869 command.add("-p"); 870 command.add(projectDir.getPath()); 871 872 // launch the command line process 873 Process process = Runtime.getRuntime().exec(command.toArray(new String[command.size()])); 874 875 876 OutputGrabber processOutput = new OutputGrabber(); 877 int status = GrabProcessOutput.grabProcessOutput( 878 process, 879 Wait.WAIT_FOR_READERS, // we really want to make sure we get all the output! 880 processOutput); 881 if (status != 0) { 882 fail(processOutput.getOutput().toString() + processOutput.getError().toString()); 883 } 884 assertEquals(0, status); 885 886 // Run ant 887 String antCmd = "ant" + (CURRENT_PLATFORM == PLATFORM_WINDOWS ? ".bat" : ""); 888 String antTarget = "debug"; 889 process = Runtime.getRuntime().exec(antCmd + " " + antTarget, null, projectDir); 890 processOutput = new OutputGrabber(); 891 status = GrabProcessOutput.grabProcessOutput( 892 process, 893 Wait.WAIT_FOR_READERS, // we really want to make sure we get all the output! 894 processOutput); 895 if (status != 0) { 896 fail(processOutput.getOutput().toString() + processOutput.getError().toString()); 897 } 898 assertEquals(0, status); 899 System.out.println("Ant succeeded (code=" + status + ")"); 900 } 901 } 902