diff options
8 files changed, 422 insertions, 24 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/repository/MavenServerFunction.java b/src/main/java/com/google/devtools/build/lib/bazel/repository/MavenServerFunction.java index f76039afb9..dd9c51ced6 100644 --- a/src/main/java/com/google/devtools/build/lib/bazel/repository/MavenServerFunction.java +++ b/src/main/java/com/google/devtools/build/lib/bazel/repository/MavenServerFunction.java @@ -67,7 +67,7 @@ public class MavenServerFunction implements SkyFunction { String repository = (String) skyKey.argument(); Rule repositoryRule = null; try { - repositoryRule = ExternalPackageUtil.getRule(repository, env); + repositoryRule = ExternalPackageUtil.getRuleByName(repository, env); } catch (ExternalPackageUtil.ExternalRuleNotFoundException ex) { // Ignored. We throw a new one below. } diff --git a/src/main/java/com/google/devtools/build/lib/rules/ExternalPackageUtil.java b/src/main/java/com/google/devtools/build/lib/rules/ExternalPackageUtil.java index bf28790912..5e395c04aa 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/ExternalPackageUtil.java +++ b/src/main/java/com/google/devtools/build/lib/rules/ExternalPackageUtil.java @@ -14,6 +14,10 @@ package com.google.devtools.build.lib.rules; +import com.google.common.base.Function; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.cmdline.LabelSyntaxException; import com.google.devtools.build.lib.cmdline.RepositoryName; @@ -33,16 +37,25 @@ import com.google.devtools.build.skyframe.SkyFunctionException; import com.google.devtools.build.skyframe.SkyFunctionException.Transience; import com.google.devtools.build.skyframe.SkyKey; import java.io.IOException; +import java.util.HashSet; +import java.util.List; +import java.util.Set; import javax.annotation.Nullable; /** Utility class to centralize looking up rules from the external package. */ public class ExternalPackageUtil { - /** Uses a rule name to fetch the corresponding Rule from the external package. */ + /** + * Loads the external package and then calls the selector to find matching rules. + * + * @param env the environment to use for lookups + * @param returnFirst whether to return only the first rule found + * @param selector the function to call to load rules + */ @Nullable - public static Rule getRule(String ruleName, Environment env) + private static Set<Rule> getRules( + Environment env, boolean returnFirst, Function<Package, Iterable<Rule>> selector) throws ExternalPackageException, InterruptedException { - SkyKey packageLookupKey = PackageLookupValue.key(Label.EXTERNAL_PACKAGE_IDENTIFIER); PackageLookupValue packageLookupValue = (PackageLookupValue) env.getValue(packageLookupKey); if (packageLookupValue == null) { @@ -50,6 +63,7 @@ public class ExternalPackageUtil { } RootedPath workspacePath = packageLookupValue.getRootedPath(Label.EXTERNAL_PACKAGE_IDENTIFIER); + Set<Rule> rules = new HashSet<>(); SkyKey workspaceKey = WorkspaceFileValue.key(workspacePath); do { WorkspaceFileValue value = (WorkspaceFileValue) env.getValue(workspaceKey); @@ -64,20 +78,77 @@ public class ExternalPackageUtil { Label.EXTERNAL_PACKAGE_IDENTIFIER, "Could not load //external package"), Transience.PERSISTENT); } - Rule rule = externalPackage.getRule(ruleName); - if (rule != null) { - return rule; + Iterable<Rule> results = selector.apply(externalPackage); + if (results != null) { + Iterables.addAll(rules, results); + if (returnFirst && !rules.isEmpty()) { + return ImmutableSet.of(Iterables.getFirst(results, null)); + } } workspaceKey = value.next(); } while (workspaceKey != null); - throw new ExternalRuleNotFoundException(ruleName); + + return rules; + } + + /** Uses a rule name to fetch the corresponding Rule from the external package. */ + @Nullable + public static List<Rule> getRuleByRuleClass(final String ruleClassName, Environment env) + throws ExternalPackageException, InterruptedException { + + Set<Rule> rules = + getRules( + env, + false, + new Function<Package, Iterable<Rule>>() { + @Nullable + @Override + public Iterable<Rule> apply(Package externalPackage) { + return externalPackage.getRulesMatchingRuleClass(ruleClassName); + } + }); + + if (env.valuesMissing()) { + return null; + } + return ImmutableList.copyOf(rules); + } + + /** Uses a rule name to fetch the corresponding Rule from the external package. */ + @Nullable + public static Rule getRuleByName(final String ruleName, Environment env) + throws ExternalPackageException, InterruptedException { + + Set<Rule> rules = + getRules( + env, + true, + new Function<Package, Iterable<Rule>>() { + @Nullable + @Override + public Iterable<Rule> apply(Package externalPackage) { + Rule rule = externalPackage.getRule(ruleName); + if (rule == null) { + return null; + } + return ImmutableList.of(rule); + } + }); + + if (env.valuesMissing()) { + return null; + } + if (rules == null || rules.isEmpty()) { + throw new ExternalRuleNotFoundException(ruleName); + } + return Iterables.getFirst(rules, null); } @Nullable public static Rule getRule(String ruleName, @Nullable String ruleClassName, Environment env) throws ExternalPackageException, InterruptedException { try { - return getRule(RepositoryName.create("@" + ruleName), ruleClassName, env); + return getRepository(RepositoryName.create("@" + ruleName), ruleClassName, env); } catch (LabelSyntaxException e) { throw new ExternalPackageException( new IOException("Invalid rule name " + ruleName), Transience.PERSISTENT); @@ -91,10 +162,10 @@ public class ExternalPackageUtil { * name. */ @Nullable - public static Rule getRule( + public static Rule getRepository( RepositoryName repositoryName, @Nullable String ruleClassName, Environment env) throws ExternalPackageException, InterruptedException { - Rule rule = getRule(repositoryName.strippedName(), env); + Rule rule = getRuleByName(repositoryName.strippedName(), env); Preconditions.checkState( rule == null || ruleClassName == null || rule.getRuleClass().equals(ruleClassName), "Got %s, was expecting a %s", diff --git a/src/main/java/com/google/devtools/build/lib/rules/repository/RepositoryDelegatorFunction.java b/src/main/java/com/google/devtools/build/lib/rules/repository/RepositoryDelegatorFunction.java index 80e0cdfe75..1ab88232c0 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/repository/RepositoryDelegatorFunction.java +++ b/src/main/java/com/google/devtools/build/lib/rules/repository/RepositoryDelegatorFunction.java @@ -116,7 +116,7 @@ public final class RepositoryDelegatorFunction implements SkyFunction { Rule rule; try { - rule = ExternalPackageUtil.getRule(repositoryName, null, env); + rule = ExternalPackageUtil.getRepository(repositoryName, null, env); } catch (ExternalPackageUtil.ExternalRuleNotFoundException e) { return RepositoryDirectoryValue.NO_SUCH_REPOSITORY_VALUE; } diff --git a/src/main/java/com/google/devtools/build/lib/rules/repository/RepositoryFunction.java b/src/main/java/com/google/devtools/build/lib/rules/repository/RepositoryFunction.java index 036cd7b056..16c29673ba 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/repository/RepositoryFunction.java +++ b/src/main/java/com/google/devtools/build/lib/rules/repository/RepositoryFunction.java @@ -394,9 +394,10 @@ public abstract class RepositoryFunction { try { // Add a dependency to the repository rule. RepositoryDirectoryValue does add this - // dependency already but we want to catch RepositoryNotFoundException, so invoke #getRule + // dependency already but we want to catch RepositoryNotFoundException, so invoke + // #getRuleByName // first. - Rule rule = ExternalPackageUtil.getRule(repositoryName, env); + Rule rule = ExternalPackageUtil.getRuleByName(repositoryName, env); if (rule == null) { return; } @@ -421,7 +422,8 @@ public abstract class RepositoryFunction { } } catch (ExternalPackageUtil.ExternalRuleNotFoundException ex) { // The repository we are looking for does not exist so we should depend on the whole - // WORKSPACE file. In that case, the call to RepositoryFunction#getRule(String, Environment) + // WORKSPACE file. In that case, the call to RepositoryFunction#getRuleByName(String, + // Environment) // already requested all repository functions from the WORKSPACE file from Skyframe as part // of the resolution. Therefore we are safe to ignore that Exception. return; diff --git a/src/test/java/com/google/devtools/build/lib/BUILD b/src/test/java/com/google/devtools/build/lib/BUILD index 92c9ee2429..7a6fa64457 100644 --- a/src/test/java/com/google/devtools/build/lib/BUILD +++ b/src/test/java/com/google/devtools/build/lib/BUILD @@ -1223,6 +1223,30 @@ java_test( ], ) +java_test( + name = "rules-tests", + srcs = glob(["rules/*.java"]), + test_class = "com.google.devtools.build.lib.AllTests", + deps = [ + ":actions_testutil", + ":analysis_testutil", + "//src/main/java/com/google/devtools/build/lib:build-base", + "//src/main/java/com/google/devtools/build/lib:collect", + "//src/main/java/com/google/devtools/build/lib:events", + "//src/main/java/com/google/devtools/build/lib:io", + "//src/main/java/com/google/devtools/build/lib:packages-internal", + "//src/main/java/com/google/devtools/build/lib:proto-rules", + "//src/main/java/com/google/devtools/build/lib/actions", + "//src/main/java/com/google/devtools/build/lib/rules/cpp", + "//src/main/java/com/google/devtools/build/skyframe", + "//src/main/java/com/google/devtools/build/skyframe:skyframe-objects", + "//third_party:auto_value", + "//third_party:guava", + "//third_party:junit4", + "//third_party:truth", + ], +) + java_binary( name = "MockSubprocess", srcs = ["windows/MockSubprocess.java"], diff --git a/src/test/java/com/google/devtools/build/lib/rules/AliasTest.java b/src/test/java/com/google/devtools/build/lib/rules/AliasTest.java index 4b456a358c..029998968f 100644 --- a/src/test/java/com/google/devtools/build/lib/rules/AliasTest.java +++ b/src/test/java/com/google/devtools/build/lib/rules/AliasTest.java @@ -26,14 +26,13 @@ import com.google.devtools.build.lib.analysis.util.BuildViewTestCase; import com.google.devtools.build.lib.collect.nestedset.NestedSet; import com.google.devtools.build.lib.packages.License.LicenseType; import com.google.devtools.build.lib.rules.cpp.CppCompilationContext; - -import org.junit.Test; - import java.util.Set; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; -/** - * Unit tests for the <code>alias</code> rule. - */ +/** Unit tests for the <code>alias</code> rule. */ +@RunWith(JUnit4.class) public class AliasTest extends BuildViewTestCase { @Test public void smoke() throws Exception { diff --git a/src/test/java/com/google/devtools/build/lib/rules/ExternalPackageUtilTest.java b/src/test/java/com/google/devtools/build/lib/rules/ExternalPackageUtilTest.java new file mode 100644 index 0000000000..27c2e775c6 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/rules/ExternalPackageUtilTest.java @@ -0,0 +1,301 @@ +// Copyright 2017 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.devtools.build.lib.rules; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.devtools.build.skyframe.EvaluationResultSubjectFactory.assertThatEvaluationResult; + +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.devtools.build.lib.analysis.BlazeDirectories; +import com.google.devtools.build.lib.analysis.util.AnalysisMock; +import com.google.devtools.build.lib.analysis.util.BuildViewTestCase; +import com.google.devtools.build.lib.cmdline.PackageIdentifier; +import com.google.devtools.build.lib.events.NullEventHandler; +import com.google.devtools.build.lib.packages.PackageFactory; +import com.google.devtools.build.lib.packages.PackageFactory.EnvironmentExtension; +import com.google.devtools.build.lib.packages.Rule; +import com.google.devtools.build.lib.packages.RuleClassProvider; +import com.google.devtools.build.lib.pkgcache.PathPackageLocator; +import com.google.devtools.build.lib.skyframe.ExternalFilesHelper; +import com.google.devtools.build.lib.skyframe.ExternalFilesHelper.ExternalFileAction; +import com.google.devtools.build.lib.skyframe.ExternalPackageFunction; +import com.google.devtools.build.lib.skyframe.FileFunction; +import com.google.devtools.build.lib.skyframe.FileStateFunction; +import com.google.devtools.build.lib.skyframe.LocalRepositoryLookupFunction; +import com.google.devtools.build.lib.skyframe.PackageLookupFunction; +import com.google.devtools.build.lib.skyframe.PackageLookupFunction.CrossRepositoryLabelViolationStrategy; +import com.google.devtools.build.lib.skyframe.PackageLookupValue.BuildFileName; +import com.google.devtools.build.lib.skyframe.PrecomputedValue; +import com.google.devtools.build.lib.skyframe.SkyFunctions; +import com.google.devtools.build.lib.skyframe.SkyframeExecutor; +import com.google.devtools.build.lib.skyframe.WorkspaceASTFunction; +import com.google.devtools.build.lib.skyframe.WorkspaceFileFunction; +import com.google.devtools.build.lib.util.io.TimestampGranularityMonitor; +import com.google.devtools.build.skyframe.EvaluationResult; +import com.google.devtools.build.skyframe.InMemoryMemoizingEvaluator; +import com.google.devtools.build.skyframe.LegacySkyKey; +import com.google.devtools.build.skyframe.MemoizingEvaluator; +import com.google.devtools.build.skyframe.RecordingDifferencer; +import com.google.devtools.build.skyframe.SequentialBuildDriver; +import com.google.devtools.build.skyframe.SkyFunction; +import com.google.devtools.build.skyframe.SkyFunctionException; +import com.google.devtools.build.skyframe.SkyFunctionName; +import com.google.devtools.build.skyframe.SkyKey; +import com.google.devtools.build.skyframe.SkyValue; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; +import javax.annotation.Nullable; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link ExternalPackageUtil}. */ +@RunWith(JUnit4.class) +public class ExternalPackageUtilTest extends BuildViewTestCase { + + private SequentialBuildDriver driver; + + @Before + public void createEnvironment() { + AnalysisMock analysisMock = AnalysisMock.get(); + AtomicReference<PathPackageLocator> pkgLocator = + new AtomicReference<>(new PathPackageLocator(outputBase, ImmutableList.of(rootDirectory))); + AtomicReference<ImmutableSet<PackageIdentifier>> deletedPackages = + new AtomicReference<>(ImmutableSet.<PackageIdentifier>of()); + BlazeDirectories directories = + new BlazeDirectories( + rootDirectory, outputBase, rootDirectory, analysisMock.getProductName()); + ExternalFilesHelper externalFilesHelper = + new ExternalFilesHelper( + pkgLocator, + ExternalFileAction.DEPEND_ON_EXTERNAL_PKG_FOR_EXTERNAL_REPO_PATHS, + directories); + + Map<SkyFunctionName, SkyFunction> skyFunctions = new HashMap<>(); + skyFunctions.put( + SkyFunctions.PACKAGE_LOOKUP, + new PackageLookupFunction( + deletedPackages, + CrossRepositoryLabelViolationStrategy.ERROR, + ImmutableList.of(BuildFileName.BUILD_DOT_BAZEL, BuildFileName.BUILD))); + skyFunctions.put( + SkyFunctions.FILE_STATE, + new FileStateFunction( + new AtomicReference<TimestampGranularityMonitor>(), externalFilesHelper)); + skyFunctions.put(SkyFunctions.FILE, new FileFunction(pkgLocator)); + RuleClassProvider ruleClassProvider = analysisMock.createRuleClassProvider(); + skyFunctions.put(SkyFunctions.WORKSPACE_AST, new WorkspaceASTFunction(ruleClassProvider)); + skyFunctions.put( + SkyFunctions.WORKSPACE_FILE, + new WorkspaceFileFunction( + ruleClassProvider, + analysisMock + .getPackageFactoryBuilderForTesting() + .setEnvironmentExtensions( + ImmutableList.<EnvironmentExtension>of( + new PackageFactory.EmptyEnvironmentExtension())) + .build(ruleClassProvider, scratch.getFileSystem()), + directories)); + skyFunctions.put(SkyFunctions.EXTERNAL_PACKAGE, new ExternalPackageFunction()); + skyFunctions.put(SkyFunctions.LOCAL_REPOSITORY_LOOKUP, new LocalRepositoryLookupFunction()); + + // Helper Skyfunctions to call ExternalPackageUtil. + skyFunctions.put(GET_RULE_BY_NAME_FUNCTION, new GetRuleByNameFunction()); + skyFunctions.put(GET_RULE_BY_RULE_CLASS_FUNCTION, new GetRuleByRuleClassFunction()); + + RecordingDifferencer differencer = new RecordingDifferencer(); + MemoizingEvaluator evaluator = new InMemoryMemoizingEvaluator(skyFunctions, differencer); + driver = new SequentialBuildDriver(evaluator); + PrecomputedValue.PATH_PACKAGE_LOCATOR.set(differencer, pkgLocator.get()); + } + + @Test + public void getRuleByName() throws Exception { + scratch.overwriteFile("WORKSPACE", "http_archive(name = 'foo', url = 'http://foo')"); + invalidatePackages(false); + + SkyKey key = getRuleByNameKey("foo"); + EvaluationResult<GetRuleByNameValue> result = getRuleByName(key); + + assertThatEvaluationResult(result).hasNoError(); + + Rule rule = result.get(key).rule(); + assertThat(rule).isNotNull(); + assertThat(rule.getName()).isEqualTo("foo"); + } + + @Test + public void getRuleByName_missing() throws Exception { + scratch.overwriteFile("WORKSPACE", "http_archive(name = 'foo', url = 'http://foo')"); + invalidatePackages(false); + + SkyKey key = getRuleByNameKey("bar"); + EvaluationResult<GetRuleByNameValue> result = getRuleByName(key); + + assertThatEvaluationResult(result) + .hasErrorEntryForKeyThat(key) + .hasExceptionThat() + .hasMessageThat() + .contains("The rule named 'bar' could not be resolved"); + } + + @Test + public void getRuleByRuleClass() throws Exception { + scratch.overwriteFile( + "WORKSPACE", + "http_archive(name = 'foo', url = 'http://foo')", + "http_archive(name = 'bar', url = 'http://bar')"); + invalidatePackages(false); + + SkyKey key = getRuleByRuleClassKey("http_archive"); + EvaluationResult<GetRuleByRuleClassValue> result = getRuleByRuleClass(key); + + assertThatEvaluationResult(result).hasNoError(); + + List<Rule> rules = result.get(key).rules(); + assertThat(rules).isNotNull(); + assertThat(rules).hasSize(2); + + Set<String> names = new HashSet<>(); + for (Rule rule : rules) { + names.add(rule.getName()); + } + + assertThat(names).containsExactly("foo", "bar"); + } + + @Test + public void getRuleByRuleClass_none() throws Exception { + scratch.overwriteFile( + "WORKSPACE", + "http_archive(name = 'foo', url = 'http://foo')", + "http_archive(name = 'bar', url = 'http://bar')"); + invalidatePackages(false); + + SkyKey key = getRuleByRuleClassKey("new_git_repository"); + EvaluationResult<GetRuleByRuleClassValue> result = getRuleByRuleClass(key); + + assertThatEvaluationResult(result).hasNoError(); + + List<Rule> rules = result.get(key).rules(); + assertThat(rules).isNotNull(); + assertThat(rules).isEmpty(); + } + + // HELPER SKYFUNCTIONS + + // GetRuleByName. + SkyKey getRuleByNameKey(String ruleName) { + return LegacySkyKey.create(GET_RULE_BY_NAME_FUNCTION, ruleName); + } + + EvaluationResult<GetRuleByNameValue> getRuleByName(SkyKey key) throws InterruptedException { + return driver.<GetRuleByNameValue>evaluate( + ImmutableList.of(key), + false, + SkyframeExecutor.DEFAULT_THREAD_COUNT, + NullEventHandler.INSTANCE); + } + + private static final SkyFunctionName GET_RULE_BY_NAME_FUNCTION = + SkyFunctionName.create("GET_RULE_BY_NAME"); + + @AutoValue + abstract static class GetRuleByNameValue implements SkyValue { + abstract Rule rule(); + + static GetRuleByNameValue create(Rule rule) { + return new AutoValue_ExternalPackageUtilTest_GetRuleByNameValue(rule); + } + } + + private static final class GetRuleByNameFunction implements SkyFunction { + + @Nullable + @Override + public SkyValue compute(SkyKey skyKey, Environment env) + throws SkyFunctionException, InterruptedException { + String ruleName = (String) skyKey.argument(); + + Rule rule = ExternalPackageUtil.getRuleByName(ruleName, env); + if (rule == null) { + return null; + } + return GetRuleByNameValue.create(rule); + } + + @Nullable + @Override + public String extractTag(SkyKey skyKey) { + return null; + } + } + + // GetRuleByRuleClass. + SkyKey getRuleByRuleClassKey(String ruleClass) { + return LegacySkyKey.create(GET_RULE_BY_RULE_CLASS_FUNCTION, ruleClass); + } + + EvaluationResult<GetRuleByRuleClassValue> getRuleByRuleClass(SkyKey key) + throws InterruptedException { + return driver.<GetRuleByRuleClassValue>evaluate( + ImmutableList.of(key), + false, + SkyframeExecutor.DEFAULT_THREAD_COUNT, + NullEventHandler.INSTANCE); + } + + private static final SkyFunctionName GET_RULE_BY_RULE_CLASS_FUNCTION = + SkyFunctionName.create("GET_RULE_BY_RULE_CLASS"); + + @AutoValue + abstract static class GetRuleByRuleClassValue implements SkyValue { + abstract ImmutableList<Rule> rules(); + + static GetRuleByRuleClassValue create(Iterable<Rule> rules) { + return new AutoValue_ExternalPackageUtilTest_GetRuleByRuleClassValue( + ImmutableList.copyOf(rules)); + } + } + + private static final class GetRuleByRuleClassFunction implements SkyFunction { + + @Nullable + @Override + public SkyValue compute(SkyKey skyKey, Environment env) + throws SkyFunctionException, InterruptedException { + String ruleName = (String) skyKey.argument(); + + List<Rule> rules = ExternalPackageUtil.getRuleByRuleClass(ruleName, env); + if (rules == null) { + return null; + } + return GetRuleByRuleClassValue.create(rules); + } + + @Nullable + @Override + public String extractTag(SkyKey skyKey) { + return null; + } + } +} diff --git a/src/test/java/com/google/devtools/build/lib/rules/ToolchainTypeTest.java b/src/test/java/com/google/devtools/build/lib/rules/ToolchainTypeTest.java index e9cbf36307..a4990af350 100644 --- a/src/test/java/com/google/devtools/build/lib/rules/ToolchainTypeTest.java +++ b/src/test/java/com/google/devtools/build/lib/rules/ToolchainTypeTest.java @@ -18,10 +18,11 @@ import static com.google.common.truth.Truth.assertThat; import com.google.devtools.build.lib.analysis.ConfiguredTarget; import com.google.devtools.build.lib.analysis.util.BuildViewTestCase; import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; -/** - * Unit tests for the {@code toolchain_type} rule. - */ +/** Unit tests for the {@code toolchain_type} rule. */ +@RunWith(JUnit4.class) public class ToolchainTypeTest extends BuildViewTestCase { @Test public void testSmoke() throws Exception { |