1 /*
2  * Copyright (C) 2008 Google Inc.
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.google.inject;
18 
19 import static com.google.common.base.StandardSystemProperty.JAVA_CLASS_PATH;
20 import static com.google.common.base.StandardSystemProperty.PATH_SEPARATOR;
21 import static com.google.inject.internal.InternalFlags.getIncludeStackTraceOption;
22 import static junit.framework.Assert.assertEquals;
23 import static junit.framework.Assert.assertNotNull;
24 import static junit.framework.Assert.assertSame;
25 import static junit.framework.Assert.assertTrue;
26 
27 import com.google.common.base.Function;
28 import com.google.common.base.Joiner;
29 import com.google.common.base.Splitter;
30 import com.google.common.collect.ImmutableList;
31 import com.google.common.collect.Iterables;
32 import com.google.common.testing.GcFinalization;
33 import com.google.inject.internal.InternalFlags.IncludeStackTraceOption;
34 import java.io.ByteArrayInputStream;
35 import java.io.ByteArrayOutputStream;
36 import java.io.File;
37 import java.io.IOException;
38 import java.io.NotSerializableException;
39 import java.io.ObjectInputStream;
40 import java.io.ObjectOutputStream;
41 import java.lang.ref.ReferenceQueue;
42 import java.lang.ref.WeakReference;
43 import java.net.MalformedURLException;
44 import java.net.URL;
45 import java.net.URLClassLoader;
46 import junit.framework.Assert;
47 
48 /** @author jessewilson@google.com (Jesse Wilson) */
49 public class Asserts {
50 
Asserts()51   private Asserts() {}
52 
53   /**
54    * Returns the String that would appear in an error message for this chain of classes as modules.
55    */
asModuleChain(Class... classes)56   public static String asModuleChain(Class... classes) {
57     return Joiner.on(" -> ")
58         .appendTo(
59             new StringBuilder(" (via modules: "),
60             Iterables.transform(
61                 ImmutableList.copyOf(classes),
62                 new Function<Class, String>() {
63                   @Override
64                   public String apply(Class input) {
65                     return input.getName();
66                   }
67                 }))
68         .append(")")
69         .toString();
70   }
71 
72   /**
73    * Returns the source file appears in error messages based on {@link
74    * #getIncludeStackTraceOption()} value.
75    */
76   public static String getDeclaringSourcePart(Class clazz) {
77     if (getIncludeStackTraceOption() == IncludeStackTraceOption.OFF) {
78       return ".configure(Unknown Source";
79     }
80     return ".configure(" + clazz.getSimpleName() + ".java:";
81   }
82 
83   /**
84    * Returns true if {@link #getIncludeStackTraceOption()} returns {@link
85    * IncludeStackTraceOption#OFF}.
86    */
87   public static boolean isIncludeStackTraceOff() {
88     return getIncludeStackTraceOption() == IncludeStackTraceOption.OFF;
89   }
90 
91   /**
92    * Returns true if {@link #getIncludeStackTraceOption()} returns {@link
93    * IncludeStackTraceOption#COMPLETE}.
94    */
95   public static boolean isIncludeStackTraceComplete() {
96     return getIncludeStackTraceOption() == IncludeStackTraceOption.COMPLETE;
97   }
98 
99   /**
100    * Fails unless {@code expected.equals(actual)}, {@code actual.equals(expected)} and their hash
101    * codes are equal. This is useful for testing the equals method itself.
102    */
103   public static void assertEqualsBothWays(Object expected, Object actual) {
104     assertNotNull(expected);
105     assertNotNull(actual);
106     assertEquals("expected.equals(actual)", actual, expected);
107     assertEquals("actual.equals(expected)", expected, actual);
108     assertEquals("hashCode", expected.hashCode(), actual.hashCode());
109   }
110 
111   /** Fails unless {@code text} includes all {@code substrings}, in order, no duplicates */
112   public static void assertContains(String text, String... substrings) {
113     assertContains(text, false, substrings);
114   }
115 
116   /**
117    * Fails unless {@code text} includes all {@code substrings}, in order, and optionally {@code
118    * allowDuplicates}.
119    */
120   public static void assertContains(String text, boolean allowDuplicates, String... substrings) {
121     /*if[NO_AOP]
122     // when we strip out bytecode manipulation, we lose the ability to generate some source lines.
123     if (text.contains("(Unknown Source)")) {
124       return;
125     }
126     end[NO_AOP]*/
127 
128     int startingFrom = 0;
129     for (String substring : substrings) {
130       int index = text.indexOf(substring, startingFrom);
131       assertTrue(
132           String.format("Expected \"%s\" to contain substring \"%s\"", text, substring),
133           index >= startingFrom);
134       startingFrom = index + substring.length();
135     }
136 
137     if (!allowDuplicates) {
138       String lastSubstring = substrings[substrings.length - 1];
139       assertTrue(
140           String.format(
141               "Expected \"%s\" to contain substring \"%s\" only once),", text, lastSubstring),
142           text.indexOf(lastSubstring, startingFrom) == -1);
143     }
144   }
145 
146   /** Fails unless {@code object} doesn't equal itself when reserialized. */
147   public static void assertEqualWhenReserialized(Object object) throws IOException {
148     Object reserialized = reserialize(object);
149     assertEquals(object, reserialized);
150     assertEquals(object.hashCode(), reserialized.hashCode());
151   }
152 
153   /** Fails unless {@code object} has the same toString value when reserialized. */
154   public static void assertSimilarWhenReserialized(Object object) throws IOException {
155     Object reserialized = reserialize(object);
156     assertEquals(object.toString(), reserialized.toString());
157   }
158 
159   public static <E> E reserialize(E original) throws IOException {
160     try {
161       ByteArrayOutputStream out = new ByteArrayOutputStream();
162       new ObjectOutputStream(out).writeObject(original);
163       ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
164       @SuppressWarnings("unchecked") // the reserialized type is assignable
165       E reserialized = (E) new ObjectInputStream(in).readObject();
166       return reserialized;
167     } catch (ClassNotFoundException e) {
168       throw new RuntimeException(e);
169     }
170   }
171 
172   public static void assertNotSerializable(Object object) throws IOException {
173     try {
174       reserialize(object);
175       Assert.fail();
176     } catch (NotSerializableException expected) {
177     }
178   }
179 
180   public static void awaitFullGc() {
181     // GcFinalization *should* do it, but doesn't work well in practice...
182     // so we put a second latch and wait for a ReferenceQueue to tell us.
183     ReferenceQueue<Object> queue = new ReferenceQueue<>();
184     WeakReference<Object> ref = new WeakReference<>(new Object(), queue);
185     GcFinalization.awaitFullGc();
186     try {
187       assertSame("queue didn't return ref in time", ref, queue.remove(5000));
188     } catch (IllegalArgumentException e) {
189       throw new RuntimeException(e);
190     } catch (InterruptedException e) {
191       throw new RuntimeException(e);
192     }
193   }
194 
195   public static void awaitClear(WeakReference<?> ref) {
196     // GcFinalization *should* do it, but doesn't work well in practice...
197     // so we put a second latch and wait for a ReferenceQueue to tell us.
198     Object data = ref.get();
199     ReferenceQueue<Object> queue = null;
200     WeakReference extraRef = null;
201     if (data != null) {
202       queue = new ReferenceQueue<>();
203       extraRef = new WeakReference<>(data, queue);
204       data = null;
205     }
206     GcFinalization.awaitClear(ref);
207     if (queue != null) {
208       try {
209         assertSame("queue didn't return ref in time", extraRef, queue.remove(5000));
210       } catch (IllegalArgumentException e) {
211         throw new RuntimeException(e);
212       } catch (InterruptedException e) {
213         throw new RuntimeException(e);
214       }
215     }
216   }
217 
218   /** Returns the URLs in the system class path. */
219   // TODO(https://github.com/google/guava/issues/2956): Use a common API once that's available.
220   public static URL[] getClassPathUrls() {
221     if (Asserts.class.getClassLoader() instanceof URLClassLoader) {
222       return ((URLClassLoader) Asserts.class.getClassLoader()).getURLs();
223     }
224     ImmutableList.Builder<URL> urls = ImmutableList.builder();
225     for (String entry : Splitter.on(PATH_SEPARATOR.value()).split(JAVA_CLASS_PATH.value())) {
226       try {
227         try {
228           urls.add(new File(entry).toURI().toURL());
229         } catch (SecurityException e) { // File.toURI checks to see if the file is a directory
230           urls.add(new URL("file", null, new File(entry).getAbsolutePath()));
231         }
232       } catch (MalformedURLException e) {
233         AssertionError error = new AssertionError("malformed class path entry: " + entry);
234         error.initCause(e);
235         throw error;
236       }
237     }
238     return urls.build().toArray(new URL[0]);
239   }
240 }
241