diff --git a/java/com/android/modules/targetprep/Android.bp b/java/com/android/modules/targetprep/Android.bp new file mode 100644 index 0000000..0e4858e --- /dev/null +++ b/java/com/android/modules/targetprep/Android.bp @@ -0,0 +1,27 @@ +// Copyright (C) 2022 The Android Open Source Project +// +// 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 { + default_applicable_licenses: ["Android-Apache-2.0"], +} +java_library_host { + name: "ClasspathFetcher", + srcs: ["ClasspathFetcher.java"], + libs: [ + "cts-tradefed", + "tradefed", + "compatibility-host-util", + "androidx.annotation_annotation", + ], +} \ No newline at end of file diff --git a/java/com/android/modules/targetprep/ClasspathFetcher.java b/java/com/android/modules/targetprep/ClasspathFetcher.java new file mode 100644 index 0000000..c4de7a3 --- /dev/null +++ b/java/com/android/modules/targetprep/ClasspathFetcher.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * 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.android.modules.targetprep; + +import com.android.tradefed.config.Option; +import com.android.tradefed.device.DeviceNotAvailableException; +import com.android.tradefed.device.ITestDevice; +import com.android.tradefed.invoker.TestInformation; +import com.android.tradefed.log.LogUtil.CLog; +import com.android.tradefed.targetprep.BaseTargetPreparer; +import com.android.tradefed.targetprep.TargetSetupError; +import com.android.tradefed.util.RunUtil; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +/* + * Target preparer that fetches classpath relevant artifacts for a test in a 'reentrant' manner. + * + *
The preparer will fetch all BOOTCLASSPATH, SYSTEMSERVERCLASSPATH and shared + * libraries from the device, parse their contents and dump the classnames into a temporary + * directory.
+ * + *Additionally, the preparer can fetch, parse and dump data for module specific artifacts + * (i.e. apk-in-apex) if specified.
+ * + *Nested runs of the artifact fetcher (i.e. in the case of a top-level test config xml and a + * child test xml) will only fetch non-common elements, and remove temporary class dumps fetched + * during that particular preparer run.
+ * + *Each module's conformance framework test config xml must run the preparer (with module + * specific parameters) before the entrypoint test jar, and the top-level conformance framework + * config xml must also run it before any other module xml.
+ * + *The upshot is that when running all conformance framework tests for all modules, the shared + * artifacts are fetched and processed only once.
+ */ +public class ClasspathFetcher extends BaseTargetPreparer { + + public static final String DEVICE_JAR_ARTIFACTS_TAG = "device-jar-artifacts"; + + private boolean mFetchedArtifacts = false; + + @Override + public void setUp(TestInformation testInfo) + throws TargetSetupError, DeviceNotAvailableException { + // The artifacts have been fetched already, no need to do anything else. + if (testInfo.properties().containsKey(DEVICE_JAR_ARTIFACTS_TAG)) { + return; + } + try { + final Path tmpDir = Files.createTempDirectory("device_artifacts"); + testInfo.properties().put(DEVICE_JAR_ARTIFACTS_TAG, tmpDir.toAbsolutePath().toString()); + // TODO(b/254647172): Fetch data + mFetchedArtifacts = true; + } catch(IOException e) { + throw new RuntimeException("Could not create temp artifacts dir!", e); + } + } + + @Override + public void tearDown(TestInformation testInfo, Throwable e) { + if (mFetchedArtifacts) { + try { + final String path = testInfo.properties().get(DEVICE_JAR_ARTIFACTS_TAG); + if (path == null) { + throw new IllegalStateException("Target preparer has previously fetched" + + " artifacts, but the DEVICE_JAR_ARTIFACTS_TAG property was removed"); + } + final File jarArtifactsDir = new File(path); + if (!jarArtifactsDir.delete()) { + throw new RuntimeException("Failed to remove jar artifacts dir!"); + } + } finally { + testInfo.properties().remove(DEVICE_JAR_ARTIFACTS_TAG); + } + } + } + +} \ No newline at end of file diff --git a/javatests/com/android/modules/targetprep/Android.bp b/javatests/com/android/modules/targetprep/Android.bp new file mode 100644 index 0000000..827cdb3 --- /dev/null +++ b/javatests/com/android/modules/targetprep/Android.bp @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * 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 { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +java_test_host { + name: "ClasspathFetcherTest", + test_options: { + unit_test: true, + }, + srcs: [ + "ClasspathFetcherTest.java" + ], + static_libs: [ + "ClasspathFetcher", + "mockito-host", + "objenesis", + "truth-prebuilt", + "junit", + "tradefed", + ], + test_suites: [ + "general-tests", + ], +} diff --git a/javatests/com/android/modules/targetprep/ClasspathFetcherTest.java b/javatests/com/android/modules/targetprep/ClasspathFetcherTest.java new file mode 100644 index 0000000..28e4957 --- /dev/null +++ b/javatests/com/android/modules/targetprep/ClasspathFetcherTest.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * 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.android.modules.targetprep; + +import static com.android.modules.targetprep.ClasspathFetcher.DEVICE_JAR_ARTIFACTS_TAG; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.fail; +import static org.junit.Assume.assumeTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + + +import com.android.tradefed.build.IDeviceBuildInfo; +import com.android.tradefed.device.ITestDevice; +import com.android.tradefed.invoker.IInvocationContext; +import com.android.tradefed.invoker.InvocationContext; +import com.android.tradefed.invoker.TestInformation; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.RuleChain; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +@RunWith(JUnit4.class) +public class ClasspathFetcherTest { + + private static final String SERIAL = "SERIAL"; + + @Mock IDeviceBuildInfo mMockBuildInfo; + @Mock ITestDevice mMockTestDevice; + + private TestInformation mTestInfo; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + when(mMockTestDevice.getSerialNumber()).thenReturn(SERIAL); + when(mMockTestDevice.getDeviceDescriptor()).thenReturn(null); + when(mMockTestDevice.isAppEnumerationSupported()).thenReturn(false); + IInvocationContext context = new InvocationContext(); + context.addAllocatedDevice("device", mMockTestDevice); + context.addDeviceBuildInfo("device", mMockBuildInfo); + mTestInfo = TestInformation.newBuilder().setInvocationContext(context).build(); + } + + @Test + public void testSingleArtifactFetcher() throws Exception { + final ClasspathFetcher fetcher = new ClasspathFetcher(); + fetcher.setUp(mTestInfo); + assertThat(mTestInfo.properties().containsKey(DEVICE_JAR_ARTIFACTS_TAG)).isTrue(); + fetcher.tearDown(mTestInfo, null); + assertThat(mTestInfo.properties().containsKey(DEVICE_JAR_ARTIFACTS_TAG)).isFalse(); + } + + @Test + public void testMultipleArtifactFetchers() throws Exception { + final ClasspathFetcher fetcher1 = new ClasspathFetcher(); + final ClasspathFetcher fetcher2 = new ClasspathFetcher(); + + fetcher1.setUp(mTestInfo); + fetcher2.setUp(mTestInfo); + assertThat(mTestInfo.properties().containsKey(DEVICE_JAR_ARTIFACTS_TAG)).isTrue(); + fetcher2.tearDown(mTestInfo, null); + assertThat(mTestInfo.properties().containsKey(DEVICE_JAR_ARTIFACTS_TAG)).isTrue(); + fetcher1.tearDown(mTestInfo, null); + assertThat(mTestInfo.properties().containsKey(DEVICE_JAR_ARTIFACTS_TAG)).isFalse(); + } +}