Skip to content

Commit

Permalink
Add test coverage support to android_local_test
Browse files Browse the repository at this point in the history
  • Loading branch information
Bencodes committed Jul 8, 2022
1 parent 183c8a4 commit 2638341
Show file tree
Hide file tree
Showing 7 changed files with 231 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ java_library(
"//src/main/java/com/google/devtools/build/lib/analysis:actions/custom_command_line",
"//src/main/java/com/google/devtools/build/lib/analysis:analysis_cluster",
"//src/main/java/com/google/devtools/build/lib/analysis:blaze_directories",
"//src/main/java/com/google/devtools/build/lib/analysis:config/execution_transition_factory",
"//src/main/java/com/google/devtools/build/lib/analysis:config/toolchain_type_requirement",
"//src/main/java/com/google/devtools/build/lib/analysis:config/transitions/composing_transition_factory",
"//src/main/java/com/google/devtools/build/lib/analysis:rule_definition_environment",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
// limitations under the License.
package com.google.devtools.build.lib.bazel.rules.android;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.devtools.build.lib.actions.Artifact;
Expand All @@ -34,6 +35,9 @@
/** An implementation for the "android_local_test" rule. */
public class BazelAndroidLocalTest extends AndroidLocalTestBase {

private static final String JACOCO_COVERAGE_RUNNER_MAIN_CLASS =
"com.google.testing.coverage.JacocoCoverageRunner";

public BazelAndroidLocalTest() {
super(BazelAndroidSemantics.INSTANCE);
}
Expand Down Expand Up @@ -76,9 +80,14 @@ protected String addCoverageSupport(
JavaTargetAttributes.Builder attributesBuilder,
String mainClass)
throws RuleErrorException {
// coverage does not yet work with android_local_test
ruleContext.throwWithRuleError("android_local_test does not yet support coverage");
return "";
// This method can be called only for *_binary/*_test targets.
Preconditions.checkNotNull(executable);

helper.addCoverageSupport();

// We do not add the instrumented jar to the runtime classpath, but provide it in the shell
// script via an environment variable.
return JACOCO_COVERAGE_RUNNER_MAIN_CLASS;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import com.google.devtools.build.lib.analysis.BaseRuleClasses;
import com.google.devtools.build.lib.analysis.RuleDefinition;
import com.google.devtools.build.lib.analysis.RuleDefinitionEnvironment;
import com.google.devtools.build.lib.analysis.config.ExecutionTransitionFactory;
import com.google.devtools.build.lib.bazel.rules.java.BazelJavaRuleClasses.BaseJavaBinaryRule;
import com.google.devtools.build.lib.packages.ImplicitOutputsFunction;
import com.google.devtools.build.lib.packages.RuleClass;
Expand Down Expand Up @@ -82,7 +83,11 @@ public RuleClass build(RuleClass.Builder builder, RuleDefinitionEnvironment envi
.removeAttribute("main_class")
.removeAttribute("resources")
.removeAttribute("use_testrunner")
.removeAttribute(":java_launcher")
.removeAttribute(":java_launcher") // Input files for test actions collecting code coverage
.add(
attr(":lcov_merger", LABEL)
.cfg(ExecutionTransitionFactory.create())
.value(BaseRuleClasses.getCoverageOutputGeneratorLabel()))
.cfg(
new ConfigFeatureFlagTransitionFactory(AndroidFeatureFlagSetProvider.FEATURE_FLAG_ATTR))
.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import com.google.devtools.build.lib.analysis.Runfiles;
import com.google.devtools.build.lib.analysis.RunfilesProvider;
import com.google.devtools.build.lib.analysis.RunfilesSupport;
import com.google.devtools.build.lib.analysis.SourceManifestAction;
import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
import com.google.devtools.build.lib.analysis.actions.Substitution;
import com.google.devtools.build.lib.analysis.actions.Template;
Expand Down Expand Up @@ -66,6 +67,7 @@
import com.google.devtools.build.lib.rules.java.OneVersionCheckActionBuilder;
import com.google.devtools.build.lib.rules.java.proto.GeneratedExtensionRegistryProvider;
import com.google.devtools.build.lib.util.OS;
import com.google.devtools.build.lib.util.Pair;
import com.google.devtools.build.lib.vfs.PathFragment;
import java.util.ArrayList;
import java.util.List;
Expand Down Expand Up @@ -292,7 +294,7 @@ public ConfiguredTarget create(RuleContext ruleContext)
originalMainClass,
filesToBuildBuilder,
javaExecutable,
/* createCoverageMetadataJar= */ true);
/* createCoverageMetadataJar= */ false);

Artifact oneVersionOutputArtifact = null;
JavaConfiguration javaConfig = ruleContext.getFragment(JavaConfiguration.class);
Expand Down Expand Up @@ -366,6 +368,58 @@ public ConfiguredTarget create(RuleContext ruleContext)

JavaInfo.Builder javaInfoBuilder = JavaInfo.Builder.create();

NestedSetBuilder<Pair<String, String>> coverageEnvironment = NestedSetBuilder.stableOrder();
NestedSetBuilder<Artifact> coverageSupportFiles = NestedSetBuilder.stableOrder();
if (ruleContext.getConfiguration().isCodeCoverageEnabled()) {

// Create an artifact that contains the runfiles relative paths of the jars on the runtime
// classpath. Using SourceManifestAction is the only reliable way to match the runfiles
// creation code.
Artifact runtimeClasspathArtifact =
ruleContext.getUniqueDirectoryArtifact(
"runtime_classpath_for_coverage",
"runtime_classpath.txt",
ruleContext.getBinOrGenfilesDirectory());
ruleContext.registerAction(
new SourceManifestAction(
SourceManifestAction.ManifestType.SOURCES_ONLY,
ruleContext.getActionOwner(),
runtimeClasspathArtifact,
new Runfiles.Builder(
ruleContext.getWorkspaceName(),
ruleContext.getConfiguration().legacyExternalRunfiles())
// This matches the code below in collectDefaultRunfiles.
.addTransitiveArtifactsWrappedInStableOrder(javaCommon.getRuntimeClasspath())
.build(),
true));
filesToBuildBuilder.add(runtimeClasspathArtifact);

// Pass the artifact through an environment variable in the coverage environment so it
// can be read by the coverage collection script.
coverageEnvironment.add(
new Pair<>(
"JAVA_RUNTIME_CLASSPATH_FOR_COVERAGE", runtimeClasspathArtifact.getExecPathString()));
// Add the file to coverageSupportFiles so it ends up as an input for the test action
// when coverage is enabled.
coverageSupportFiles.add(runtimeClasspathArtifact);

// Make single jar reachable from the coverage environment because it needs to be executed
// by the coverage collection script.
Artifact singleJar = JavaToolchainProvider.from(ruleContext).getSingleJar();
coverageEnvironment.add(new Pair<>("SINGLE_JAR_TOOL", singleJar.getExecPathString()));
coverageSupportFiles.add(singleJar);
}

javaCommon.addTransitiveInfoProviders(
builder,
javaInfoBuilder,
filesToBuild,
classJar,
coverageEnvironment.build(),
coverageSupportFiles.build());
javaCommon.addGenJarsProvider(
builder, javaInfoBuilder, outputs.genClass(), outputs.genSource());

javaCommon.addTransitiveInfoProviders(builder, javaInfoBuilder, filesToBuild, classJar);
javaCommon.addGenJarsProvider(
builder, javaInfoBuilder, outputs.genClass(), outputs.genSource());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,16 +69,6 @@ public void testDisallowPrecompiledJars() throws Exception {
" srcs = ['lib.jar'])");
}

@Test
public void testCoverageThrowsError() throws Exception {
useConfiguration("--collect_code_coverage");
checkError("java/test",
"test",
"android_local_test does not yet support coverage",
"android_local_test(name = 'test',",
" srcs = ['test.java'])");
}

@Test
public void testNoAndroidAllJarsPropertiesFileThrowsError() throws Exception {
checkError("java/test",
Expand Down
19 changes: 19 additions & 0 deletions src/test/shell/bazel/android/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,25 @@ android_sh_test(
],
)

android_sh_test(
name = "android_local_test_integration_test",
size = "large",
srcs = ["android_local_test_integration_test.sh"],
data = [
":android_helper",
# TODO(b/226204219): Remove this dep once Bazel properly loads d8
# in create_android_sdk_rules()
"//external:android/d8_jar_import",
"//external:android_sdk_for_testing",
"//src/test/shell/bazel:test-deps",
],
# See https://github.com/bazelbuild/bazel/issues/8235
tags = [
"no-remote",
"no_windows",
],
)

android_sh_test(
name = "aapt_integration_test",
size = "large",
Expand Down
138 changes: 138 additions & 0 deletions src/test/shell/bazel/android/android_local_test_integration_test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
#!/bin/bash

#
# Copyright 2015 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.

# For these tests to run do the following:
#
# 1. Install an Android SDK from https://developer.android.com
# 2. Set the $ANDROID_HOME environment variable
# 3. Uncomment the line in WORKSPACE containing android_sdk_repository
#
# Note that if the environment is not set up as above android_integration_test
# will silently be ignored and will be shown as passing.

# Load the test setup defined in the parent directory
CURRENT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

source "${CURRENT_DIR}/android_helper.sh" \
|| { echo "android_helper.sh not found!" >&2; exit 1; }
fail_if_no_android_sdk

source "${CURRENT_DIR}/../../integration_test_setup.sh" \
|| { echo "integration_test_setup.sh not found!" >&2; exit 1; }

if [[ "$1" = '--with_platforms' ]]; then
# TODO(b/161709111): With platforms, the below fails with
# "no attribute `$android_sdk_toolchain_type`" on AspectAwareAttributeMapper.
echo "android_local_test_integration_test.sh does not support --with_platforms!" >&2
exit 0
fi

function setup_android_local_test_env() {
mkdir -p java/com/bin/res/values
mkdir -p javatests/com/bin

# Targets for android_local_test to depend on
cat > java/com/bin/BUILD <<EOF
android_library(
name = 'lib',
manifest = 'AndroidManifest.xml',
exports_manifest = 0,
srcs = ['Bar.java'],
visibility = ["//visibility:public"],
)
EOF
cat > java/com/bin/AndroidManifest.xml <<EOF
<?xml version="1.0" encoding="utf-8"?>
<manifest package='com.bin' xmlns:android="http://schemas.android.com/apk/res/android" />
EOF
cat > java/com/bin/Bar.java <<EOF
package com.bin;
public class Bar { }
EOF
cat > java/com/bin/res/values/values.xml <<EOF
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android">
</resources>
EOF

# android_local_test targets
cat > javatests/com/bin/robolectric-deps.properties <<EOF
EOF

cat > javatests/com/bin/BUILD <<EOF
java_library(
name = 'robolectric-deps',
data = ['robolectric-deps.properties'],
)
android_local_test(
name = 'test',
srcs = ['BarTest.java'],
manifest = 'AndroidManifest.xml',
test_class = "com.bin.BarTest",
deps = ['//java/com/bin:lib', ':robolectric-deps'],
)
EOF
cat > javatests/com/bin/AndroidManifest.xml <<EOF
<?xml version="1.0" encoding="utf-8"?>
<manifest package='com.bin.test' xmlns:android='http://schemas.android.com/apk/res/android'>
</manifest>
EOF
cat > javatests/com/bin/BarTest.java <<EOF
package com.bin;
import org.junit.Test;
public class BarTest {
@Test
public void testBar() {
new Bar();
}
}
EOF
}

# Asserts that the coverage file exists by looking at the current $TEST_log.
# The method fails if TEST_log does not contain any coverage report for a passed test.
function assert_coverage_file_exists() {
local ending_part="$(sed -n -e '/PASSED/,$p' "$TEST_log")"
local coverage_file_path=$(grep -Eo "/[/a-zA-Z0-9\.\_\-]+\.dat$" <<< "$ending_part")
[[ -e "$coverage_file_path" ]] || fail "Coverage output file does not exist!"
}

function test_hello_world_android_local_test() {
create_new_workspace
setup_android_sdk_support
setup_android_local_test_env

bazel clean
bazel test --test_output=all \
//javatests/com/bin:test &>$TEST_log || fail "Tests for //javatests/com/bin:test failed"
}

function test_hello_world_android_local_test_with_coverage() {
create_new_workspace
setup_android_sdk_support
setup_android_local_test_env

bazel clean
bazel coverage --test_output=all \
//javatests/com/bin:test &>$TEST_log || fail "Test with coverage for //javatests/com/bin:test failed"

assert_coverage_file_exists
}

run_suite "android_local_test integration tests"

0 comments on commit 2638341

Please sign in to comment.