1/*
2 * Copyright (C) 2013 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
17package com.android.dx.merge;
18
19import com.android.dex.Dex;
20import com.android.dex.DexIndexOverflowException;
21import com.android.dx.command.dexer.DxContext;
22
23import java.io.File;
24import java.io.IOException;
25
26import java.util.concurrent.atomic.AtomicBoolean;
27import java.util.concurrent.Executors;
28import java.util.concurrent.ExecutorService;
29import java.util.concurrent.TimeUnit;
30import java.util.Arrays;
31import java.util.Random;
32import java.util.HashSet;
33
34/**
35 * This test tries to merge given dex files at random, a first pass at 2 by 2, followed by
36 * a second pass doing multi-way merges.
37 */
38public class MergeTest {
39
40  private static final int NUMBER_OF_TRIES = 1000;
41
42  private static final int WORKER_THREADS = 4;
43
44  private static final ExecutorService executor = Executors.newFixedThreadPool(WORKER_THREADS);
45
46  // Helper task to concurrently run merge tests.
47  static class MergeTask implements Runnable {
48    private final DexMerger dexMerger;
49    private final String[] dexFiles;
50
51    MergeTask(String[] dexFiles, Dex[] dexesToMerge) throws IOException {
52      this.dexMerger = new DexMerger(dexesToMerge, CollisionPolicy.KEEP_FIRST, new DxContext());
53      this.dexFiles = dexFiles;
54    }
55
56    public void run() {
57      try {
58        dexMerger.merge();
59      } catch (DexIndexOverflowException e) {
60        // ignore index overflow
61      } catch (Throwable t) {
62        System.err.println("Exception processing DEX files: " + t);
63        System.err.println("Problem merging those dexes: " + Arrays.toString(dexFiles));
64        System.exit(1);
65      }
66    }
67  }
68
69  public static void main(String[] args) throws Throwable {
70    Random random = new Random();
71    HashSet<Integer> seen = new HashSet<>();
72    for (int pass = 0; pass < 2; pass++) {
73      for (int i = 0; i < NUMBER_OF_TRIES; i++) {
74        // On the first pass only do 2-way merges, then do from 3 to 10 way merges
75        // but not more to avoid dex index overflow.
76        int numDex = pass == 0 ? 2 : random.nextInt(8) + 3;
77        numDex = Math.min(numDex, args.length);
78        String[] fileNames = new String[numDex];
79        for (int j = 0; j < numDex; ++j) {
80          int fileIndex = random.nextInt(args.length);
81          fileNames[j] = args[fileIndex];
82        }
83
84        if (!seen.add(fileNames.hashCode())) {
85          // Skip, already seen set of file names with the same hash.
86          continue;
87        }
88
89        Dex[] dexesToMerge = new Dex[numDex];
90        for (int j = 0; j < numDex; ++j) {
91          try {
92            dexesToMerge[j] = new Dex(new File(fileNames[j]));
93          } catch (IOException e) {
94            System.err.println("Error opening " + fileNames[j]);
95            System.err.println(e);
96            System.exit(1);
97          }
98        }
99        executor.execute(new MergeTask(fileNames, dexesToMerge));
100      }
101    }
102    executor.shutdown();
103    executor.awaitTermination(8, TimeUnit.HOURS);
104  }
105}
106