1 /*
2  * Copyright (C) 2018 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 package com.android.compatibility.common.tradefed.presubmit;
17 
18 import static org.junit.Assert.fail;
19 
20 import com.android.tradefed.config.ConfigurationException;
21 
22 import com.google.common.collect.ImmutableSet;
23 
24 import org.junit.Test;
25 import org.junit.runner.RunWith;
26 import org.junit.runners.JUnit4;
27 
28 import java.io.File;
29 import java.io.IOException;
30 import java.util.ArrayList;
31 import java.util.Enumeration;
32 import java.util.LinkedHashMap;
33 import java.util.LinkedList;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.Set;
37 import java.util.jar.JarEntry;
38 import java.util.jar.JarFile;
39 
40 /**
41  * Test to check for duplicate files in different jars and prevent the same dependencies of being
42  * included several time (which might result in version conflicts).
43  */
44 @RunWith(JUnit4.class)
45 public class DupFileTest {
46 
47     // We ignore directories part of the common java and google packages.
48     private static final String[] IGNORE_DIRS =
49             new String[] {
50                 "android/",
51                 "javax/annotation/",
52                 "com/google/protobuf/",
53                 "kotlin/",
54                 "perfetto/protos/"
55             };
56     // Temporarily exclude some Tradefed jar while we work on unbundling them.
57     private static final Set<String> IGNORE_JARS = ImmutableSet.of("tradefed-test-framework.jar");
58 
59     /** test if there are duplicate files in different jars. */
60     @Test
testDupFilesExist()61     public void testDupFilesExist() throws Exception {
62         // Get list of jars.
63         List<File> jars = getListOfBuiltJars();
64 
65         // Create map of files to jars.
66         Map<String, List<String>> filesToJars = getMapOfFilesAndJars(jars);
67 
68         // Check if there are any files with the same name in diff jars.
69         int dupedFiles = 0;
70         StringBuilder dupedFilesSummary = new StringBuilder();
71         for (Map.Entry<String, List<String>> entry : filesToJars.entrySet()) {
72             String file = entry.getKey();
73             List<String> jarFiles = entry.getValue();
74 
75             if (jarFiles.size() != 1) {
76                 dupedFiles++;
77                 dupedFilesSummary.append(file + ": " + jarFiles.toString() + "\n");
78             }
79         }
80 
81         if (dupedFiles != 0) {
82             fail(
83                     String.format(
84                             "%d files are duplicated in different jars:\n%s",
85                             dupedFiles, dupedFilesSummary.toString()));
86         }
87     }
88 
89     /** Create map of file to jars */
getMapOfFilesAndJars(List<File> jars)90     private Map<String, List<String>> getMapOfFilesAndJars(List<File> jars) throws IOException {
91         Map<String, List<String>> map = new LinkedHashMap<String, List<String>>();
92         JarFile jarFile;
93         List<String> jarFileList;
94         // Map all the files from all the jars.
95         for (File jar : jars) {
96             if (IGNORE_JARS.contains(jar.getName())) {
97                 continue;
98             }
99             jarFile = new JarFile(jar);
100             jarFileList = getListOfFiles(jarFile);
101             jarFile.close();
102 
103             // Add in the jar file to the map.
104             for (String file : jarFileList) {
105                 if (!map.containsKey(file)) {
106                     map.put(file, new LinkedList<String>());
107                 }
108 
109                 map.get(file).add(jar.getName());
110             }
111         }
112         return map;
113     }
114 
115     /** Get the list of jars specified in the path. */
getListOfBuiltJars()116     private List<File> getListOfBuiltJars() throws ConfigurationException {
117         String classpathStr = System.getProperty("java.class.path");
118         if (classpathStr == null) {
119             throw new ConfigurationException(
120                     "Could not find the classpath property: java.class.path");
121         }
122         List<File> listOfJars = new ArrayList<File>();
123         for (String jar : classpathStr.split(":")) {
124             File jarFile = new File(jar);
125             if (jarFile.exists()) {
126                 listOfJars.add(jarFile);
127             }
128         }
129         return listOfJars;
130     }
131 
132     /** Return the list of files in the jar. */
getListOfFiles(JarFile jar)133     private List<String> getListOfFiles(JarFile jar) {
134         List<String> files = new ArrayList<String>();
135         Enumeration<JarEntry> e = jar.entries();
136         while (e.hasMoreElements()) {
137             JarEntry entry = e.nextElement();
138             String filename = entry.getName();
139             if (checkThisFile(filename)) {
140                 files.add(filename);
141             }
142         }
143         return files;
144     }
145 
146     /** Check if we should add this file to list of files. We only want to check for classes. */
checkThisFile(String filename)147     private Boolean checkThisFile(String filename) {
148         if (!filename.endsWith(".class")) {
149             return false;
150         }
151 
152         for (String skipDir : IGNORE_DIRS) {
153             if (filename.startsWith(skipDir)) {
154                 return false;
155             }
156         }
157 
158         return true;
159     }
160 }
161