1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
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 
17 package com.android.adservices.room.test;
18 
19 import android.cts.install.lib.host.InstallUtilsHost;
20 
21 import com.android.tradefed.config.Option;
22 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
23 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
24 import com.android.tradefed.util.FileUtil;
25 
26 
27 import org.junit.Test;
28 import org.junit.runner.RunWith;
29 
30 import java.io.File;
31 import java.io.IOException;
32 import java.util.Arrays;
33 import java.util.Map;
34 import java.util.regex.Pattern;
35 import java.util.stream.Collectors;
36 import java.util.zip.ZipEntry;
37 import java.util.zip.ZipFile;
38 
39 /**
40  * Asserts Room database version get bumped up between M-trains.
41  *
42  * <p>Since the Room json schema file only live in the UnitTest package, we need to download and
43  * read the json from the zip.
44  *
45  * <p>The test will compare the highest version between mainline branch and M-train release build.
46  *
47  * <p>If M-train build contains a certain DB, then:
48  *
49  * <ol>
50  *   <li>The DB must present in the new version;
51  *   <li>If highest version in the new version is the same, the schema file should stay same; and
52  *   <li>Database version should never go down.
53  * </ol>
54  */
55 @RunWith(DeviceJUnit4ClassRunner.class)
56 public class RoomDatabaseVersionBumpGuardrailTest extends BaseHostJUnit4Test {
57 
58     // This is not a perfect matcher as it could be fragile of file with other naming strategy.
59     private static final Pattern SCHEMA_FILE_PATTERN =
60             Pattern.compile("^assets/com\\.android\\..*/\\d*\\.json$");
61     public static final String AD_SERVICES_SERVICE_CORE_UNIT_TESTS_APK_FILE_NAME =
62             "AdServicesServiceCoreUnitTests.apk";
63     protected final InstallUtilsHost mHostUtils = new InstallUtilsHost(this);
64 
65     // The schema lib should be configured in gcl to point to the right package.
66     @Option(name = "base-schema-lib")
67     protected String mBaseSchemaLib;
68 
69     @Option(name = "new-schema-lib")
70     protected String mNewSchemaLib;
71 
72     @Option(name = "schema-apk-name")
73     protected String mSchemaApkName = AD_SERVICES_SERVICE_CORE_UNIT_TESTS_APK_FILE_NAME;
74 
75     @Test
roomDatabaseVersionBumpGuardrailTest()76     public void roomDatabaseVersionBumpGuardrailTest() throws Exception {
77         ZipFile baseSchemas = getTestPackageFromFile(mBaseSchemaLib);
78         ZipFile newSchemas = getTestPackageFromFile(mNewSchemaLib);
79 
80         StringBuilder errors = new StringBuilder();
81         Map<String, ZipEntry> newSchemasByFileName =
82                 newSchemas.stream()
83                         .filter(f -> SCHEMA_FILE_PATTERN.matcher(f.getName()).matches())
84                         .collect(Collectors.toMap(ZipEntry::getName, e -> e));
85 
86         for (ZipEntry baseFile :
87                 baseSchemas.stream()
88                         .filter(f -> SCHEMA_FILE_PATTERN.matcher(f.getName()).matches())
89                         .collect(Collectors.toList())) {
90             ZipEntry newFile = newSchemasByFileName.get(baseFile.getName());
91             if (newFile == null) {
92                 errors.append(
93                         String.format(
94                                 "Database json file %s is removed in the new version. Please add"
95                                         + " back.\n",
96                                 baseFile.getName()));
97                 continue;
98             }
99             if (!Arrays.equals(
100                     baseSchemas.getInputStream(baseFile).readAllBytes(),
101                     newSchemas.getInputStream(newFile).readAllBytes())) {
102                 errors.append(
103                         String.format(
104                                 "Database json file '%s' changed between major build. Please revert"
105                                         + " change and/or bump up DB version.\n",
106                                 baseFile.getName()));
107             }
108         }
109 
110         if (errors.length() != 0) {
111             throw new IllegalStateException(errors.toString());
112         }
113     }
114 
getTestPackageFromFile(String lib)115     private ZipFile getTestPackageFromFile(String lib) throws IOException {
116         File testPackage = mHostUtils.getTestFile(lib);
117         return new ZipFile(FileUtil.findFile(testPackage, mSchemaApkName));
118     }
119 }
120