• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.result.suite;
17 
18 import com.android.compatibility.common.util.ChecksumReporter.ChecksumValidationException;
19 import com.android.tradefed.result.TestDescription;
20 import com.android.tradefed.result.TestResult;
21 import com.android.tradefed.result.TestRunResult;
22 import com.android.tradefed.result.TestStatus;
23 import com.android.tradefed.result.suite.XmlSuiteResultFormatter;
24 
25 import com.google.common.hash.BloomFilter;
26 import com.google.common.hash.Funnels;
27 
28 import java.io.BufferedInputStream;
29 import java.io.BufferedOutputStream;
30 import java.io.File;
31 import java.io.FileInputStream;
32 import java.io.FileOutputStream;
33 import java.io.IOException;
34 import java.io.InputStream;
35 import java.io.ObjectOutput;
36 import java.io.ObjectOutputStream;
37 import java.io.OutputStream;
38 import java.security.DigestException;
39 import java.security.MessageDigest;
40 import java.security.NoSuchAlgorithmException;
41 import java.util.Collection;
42 import java.util.HashMap;
43 import java.util.Map.Entry;
44 
45 /**
46  * Helper to generate the checksum of the results and files. Use
47  * {@link #tryCreateChecksum(File, Collection, String)} to get the checksum file in the result dir.
48  */
49 public class CertificationChecksumHelper {
50 
51     public static final String NAME = "checksum-suite.data";
52 
53     private static final double DEFAULT_FPP = 0.05;
54     private static final String SEPARATOR = "/";
55 
56     private static final short CURRENT_VERSION = 1;
57     // Serialized format Id (ie magic number) used to identify serialized data.
58     static final short SERIALIZED_FORMAT_CODE = 650;
59 
60     private final BloomFilter<CharSequence> mResultChecksum;
61     private final HashMap<String, byte[]> mFileChecksum;
62     private final short mVersion;
63     private final String mBuildFingerprint;
64 
65     /**
66      * Create new instance of {@link CertificationChecksumHelper}
67      *
68      * @param totalCount the total number of module and test results that will be stored
69      * @param fpp the false positive percentage for result lookup misses
70      * @param version
71      * @param buildFingerprint
72      */
CertificationChecksumHelper( int totalCount, double fpp, short version, String buildFingerprint)73     public CertificationChecksumHelper(
74             int totalCount, double fpp, short version, String buildFingerprint) {
75         mResultChecksum = BloomFilter.create(Funnels.unencodedCharsFunnel(), totalCount, fpp);
76         mFileChecksum = new HashMap<>();
77         mVersion = version;
78         mBuildFingerprint = buildFingerprint;
79     }
80 
81     /**
82      * Calculate checksum of test results and files in result directory and write to disk
83      * @param dir test results directory
84      * @param results test results
85      * @return true if successful, false if unable to calculate or store the checksum
86      */
tryCreateChecksum(File dir, Collection<TestRunResult> results, String buildFingerprint)87     public static boolean tryCreateChecksum(File dir, Collection<TestRunResult> results,
88             String buildFingerprint) {
89         try {
90             // The total number of module result signatures, module summary signatures and test
91             // result signatures.
92             int totalCount = results.size() * 2 + countTestResults(results);
93             CertificationChecksumHelper checksumReporter =
94                     new CertificationChecksumHelper(totalCount, DEFAULT_FPP, CURRENT_VERSION,
95                             buildFingerprint);
96             checksumReporter.addResults(results);
97             checksumReporter.addDirectory(dir);
98             checksumReporter.saveToFile(dir);
99         } catch (Exception e) {
100             return false;
101         }
102         return true;
103     }
104 
105     /***
106      * Write the checksum data to disk.
107      * Overwrites existing file
108      * @param directory
109      * @throws IOException
110      */
saveToFile(File directory)111     private void saveToFile(File directory) throws IOException {
112         File file = new File(directory, NAME);
113 
114         try (FileOutputStream fileStream = new FileOutputStream(file, false);
115              OutputStream outputStream = new BufferedOutputStream(fileStream);
116              ObjectOutput objectOutput = new ObjectOutputStream(outputStream)) {
117             objectOutput.writeShort(SERIALIZED_FORMAT_CODE);
118             objectOutput.writeShort(mVersion);
119             objectOutput.writeObject(mResultChecksum);
120             objectOutput.writeObject(mFileChecksum);
121         }
122     }
123 
countTestResults(Collection<TestRunResult> results)124     private static int countTestResults(Collection<TestRunResult> results) {
125         int count = 0;
126         for (TestRunResult result : results) {
127             count += result.getNumTests();
128         }
129         return count;
130     }
131 
addResults(Collection<TestRunResult> results)132     private void addResults(Collection<TestRunResult> results) {
133         for (TestRunResult moduleResult : results) {
134             // First the module result signature
135             mResultChecksum.put(
136                     generateModuleResultSignature(moduleResult, mBuildFingerprint));
137             // Second the module summary signature
138             mResultChecksum.put(
139                     generateModuleSummarySignature(moduleResult, mBuildFingerprint));
140 
141             for (Entry<TestDescription, TestResult> caseResult
142                     : moduleResult.getTestResults().entrySet()) {
143                 mResultChecksum.put(generateTestResultSignature(
144                         caseResult, moduleResult, mBuildFingerprint));
145             }
146         }
147     }
148 
generateModuleResultSignature(TestRunResult module, String buildFingerprint)149     private static String generateModuleResultSignature(TestRunResult module,
150             String buildFingerprint) {
151         StringBuilder sb = new StringBuilder();
152         sb.append(buildFingerprint).append(SEPARATOR)
153                 .append(module.getName()).append(SEPARATOR)
154                 .append(module.isRunComplete()).append(SEPARATOR)
155                 .append(module.getNumTestsInState(TestStatus.FAILURE));
156         return sb.toString();
157     }
158 
generateModuleSummarySignature(TestRunResult module, String buildFingerprint)159     private static String generateModuleSummarySignature(TestRunResult module,
160             String buildFingerprint) {
161         StringBuilder sb = new StringBuilder();
162         sb.append(buildFingerprint).append(SEPARATOR)
163                 .append(module.getName()).append(SEPARATOR)
164                 .append(module.getNumTestsInState(TestStatus.FAILURE));
165         return sb.toString();
166     }
167 
generateTestResultSignature( Entry<TestDescription, TestResult> testResult, TestRunResult module, String buildFingerprint)168     private static String generateTestResultSignature(
169             Entry<TestDescription, TestResult> testResult, TestRunResult module,
170             String buildFingerprint) {
171         StringBuilder sb = new StringBuilder();
172         String stacktrace = testResult.getValue().getStackTrace();
173 
174         stacktrace = stacktrace == null ? "" : stacktrace.trim();
175         // Truncates and sanitizes the full stack trace to get consistent with {@link
176         // XmlSuiteResultFormatter}.
177         stacktrace =
178                 XmlSuiteResultFormatter.truncateStackTrace(
179                         stacktrace, testResult.getKey().getTestName());
180         stacktrace = XmlSuiteResultFormatter.sanitizeXmlContent(stacktrace);
181 
182         // Line endings for stacktraces are somewhat unpredictable and there is no need to
183         // actually read the result they are all removed for consistency.
184         stacktrace = stacktrace.replaceAll("\\r?\\n|\\r", "");
185         String testResultStatus =
186                 TestStatus.convertToCompatibilityString(testResult.getValue().getResultStatus());
187         sb.append(buildFingerprint)
188                 .append(SEPARATOR)
189                 .append(module.getName())
190                 .append(SEPARATOR)
191                 .append(testResult.getKey().toString())
192                 .append(SEPARATOR)
193                 .append(testResultStatus)
194                 .append(SEPARATOR)
195                 .append(stacktrace)
196                 .append(SEPARATOR);
197         return sb.toString();
198     }
199 
200     /***
201      * Adds all child files recursively through all sub directories
202      * @param directory target that is deeply searched for files
203      */
addDirectory(File directory)204     public void addDirectory(File directory) {
205         addDirectory(directory, directory.getName());
206     }
207 
208     /***
209      * @param path the relative path to the current directory from the base directory
210      */
addDirectory(File directory, String path)211     private void addDirectory(File directory, String path) {
212         for(String childName : directory.list()) {
213             File child = new File(directory, childName);
214             if (child.isDirectory()) {
215                 addDirectory(child, path + SEPARATOR + child.getName());
216             } else {
217                 addFile(child, path);
218             }
219         }
220     }
221 
222     /***
223      * Calculate CRC of file and store the result
224      * @param file crc calculated on this file
225      * @param path part of the key to identify the files crc
226      */
addFile(File file, String path)227     private void addFile(File file, String path) {
228         byte[] crc;
229         try {
230             crc = calculateFileChecksum(file);
231         } catch (ChecksumValidationException e) {
232             crc = new byte[0];
233         }
234         String key = path + SEPARATOR + file.getName();
235         mFileChecksum.put(key, crc);
236     }
237 
calculateFileChecksum(File file)238     private static byte[] calculateFileChecksum(File file) throws ChecksumValidationException {
239 
240         try (FileInputStream fis = new FileInputStream(file);
241              InputStream inputStream = new BufferedInputStream(fis)) {
242             MessageDigest hashSum = MessageDigest.getInstance("SHA-256");
243             int cnt;
244             int bufferSize = 8192;
245             byte [] buffer = new byte[bufferSize];
246             while ((cnt = inputStream.read(buffer)) != -1) {
247                 hashSum.update(buffer, 0, cnt);
248             }
249 
250             byte[] partialHash = new byte[32];
251             hashSum.digest(partialHash, 0, 32);
252             return partialHash;
253         } catch (NoSuchAlgorithmException e) {
254             throw new ChecksumValidationException("Unable to hash file.", e);
255         } catch (IOException e) {
256             throw new ChecksumValidationException("Unable to hash file.", e);
257         } catch (DigestException e) {
258             throw new ChecksumValidationException("Unable to hash file.", e);
259         }
260     }
261 }
262