• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2012 The Guava Authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5  * in compliance with the License. You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the License
10  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11  * or implied. See the License for the specific language governing permissions and limitations under
12  * the License.
13  */
14 
15 package com.google.common.io;
16 
17 import static com.google.common.io.BaseEncoding.base16;
18 import static com.google.common.io.BaseEncoding.base32;
19 import static com.google.common.io.BaseEncoding.base32Hex;
20 import static com.google.common.io.BaseEncoding.base64;
21 import static com.google.common.io.BaseEncoding.base64Url;
22 import static com.google.common.truth.Truth.assertThat;
23 import static java.nio.charset.StandardCharsets.UTF_8;
24 
25 import com.google.common.annotations.GwtCompatible;
26 import com.google.common.annotations.GwtIncompatible;
27 import com.google.common.base.Ascii;
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.io.BaseEncoding.DecodingException;
32 import java.io.IOException;
33 import java.io.InputStream;
34 import java.io.OutputStream;
35 import java.io.Reader;
36 import java.io.StringReader;
37 import java.io.StringWriter;
38 import junit.framework.TestCase;
39 import org.checkerframework.checker.nullness.qual.Nullable;
40 
41 /**
42  * Tests for {@code BaseEncoding}.
43  *
44  * @author Louis Wasserman
45  */
46 @GwtCompatible(emulated = true)
47 public class BaseEncodingTest extends TestCase {
48 
testSeparatorsExplicitly()49   public void testSeparatorsExplicitly() {
50     testEncodes(base64().withSeparator("\n", 3), "foobar", "Zm9\nvYm\nFy");
51     testEncodes(base64().withSeparator("$", 4), "foobar", "Zm9v$YmFy");
52     testEncodes(base32().withSeparator("*", 4), "foobar", "MZXW*6YTB*OI==*====");
53   }
54 
testSeparatorSameAsPadChar()55   public void testSeparatorSameAsPadChar() {
56     try {
57       base64().withSeparator("=", 3);
58       fail("Expected IllegalArgumentException");
59     } catch (IllegalArgumentException expected) {
60     }
61 
62     try {
63       base64().withPadChar('#').withSeparator("!#!", 3);
64       fail("Expected IllegalArgumentException");
65     } catch (IllegalArgumentException expected) {
66     }
67   }
68 
testAtMostOneSeparator()69   public void testAtMostOneSeparator() {
70     BaseEncoding separated = base64().withSeparator("\n", 3);
71     try {
72       separated.withSeparator("$", 4);
73       fail("Expected UnsupportedOperationException");
74     } catch (UnsupportedOperationException expected) {
75     }
76   }
77 
testBase64()78   public void testBase64() {
79     // The following test vectors are specified in RFC 4648 itself
80     testEncodingWithSeparators(base64(), "", "");
81     testEncodingWithSeparators(base64(), "f", "Zg==");
82     testEncodingWithSeparators(base64(), "fo", "Zm8=");
83     testEncodingWithSeparators(base64(), "foo", "Zm9v");
84     testEncodingWithSeparators(base64(), "foob", "Zm9vYg==");
85     testEncodingWithSeparators(base64(), "fooba", "Zm9vYmE=");
86     testEncodingWithSeparators(base64(), "foobar", "Zm9vYmFy");
87   }
88 
89   @GwtIncompatible // Reader/Writer
testBase64Streaming()90   public void testBase64Streaming() throws IOException {
91     // The following test vectors are specified in RFC 4648 itself
92     testStreamingEncodingWithSeparators(base64(), "", "");
93     testStreamingEncodingWithSeparators(base64(), "f", "Zg==");
94     testStreamingEncodingWithSeparators(base64(), "fo", "Zm8=");
95     testStreamingEncodingWithSeparators(base64(), "foo", "Zm9v");
96     testStreamingEncodingWithSeparators(base64(), "foob", "Zm9vYg==");
97     testStreamingEncodingWithSeparators(base64(), "fooba", "Zm9vYmE=");
98     testStreamingEncodingWithSeparators(base64(), "foobar", "Zm9vYmFy");
99   }
100 
testBase64LenientPadding()101   public void testBase64LenientPadding() {
102     testDecodes(base64(), "Zg", "f");
103     testDecodes(base64(), "Zg=", "f");
104     testDecodes(base64(), "Zg==", "f"); // proper padding length
105     testDecodes(base64(), "Zg===", "f");
106     testDecodes(base64(), "Zg====", "f");
107   }
108 
testBase64InvalidDecodings()109   public void testBase64InvalidDecodings() {
110     // These contain bytes not in the decodabet.
111     assertFailsToDecode(base64(), "A\u007f", "Unrecognized character: 0x7f");
112     assertFailsToDecode(base64(), "Wf2!", "Unrecognized character: !");
113     // This sentence just isn't base64() encoded.
114     assertFailsToDecode(base64(), "let's not talk of love or chains!");
115     // A 4n+1 length string is never legal base64().
116     assertFailsToDecode(base64(), "12345", "Invalid input length 5");
117     // These have a combination of invalid length, unrecognized characters and wrong padding.
118     assertFailsToDecode(base64(), "AB=C", "Unrecognized character: =");
119     assertFailsToDecode(base64(), "A=BCD", "Invalid input length 5");
120     assertFailsToDecode(base64(), "?", "Invalid input length 1");
121   }
122 
testBase64CannotUpperCase()123   public void testBase64CannotUpperCase() {
124     try {
125       base64().upperCase();
126       fail();
127     } catch (IllegalStateException expected) {
128     }
129   }
130 
testBase64CannotLowerCase()131   public void testBase64CannotLowerCase() {
132     try {
133       base64().lowerCase();
134       fail();
135     } catch (IllegalStateException expected) {
136     }
137   }
138 
testBase64CannotIgnoreCase()139   public void testBase64CannotIgnoreCase() {
140     try {
141       base64().ignoreCase();
142       fail();
143     } catch (IllegalStateException expected) {
144     }
145   }
146 
testBase64AlternatePadding()147   public void testBase64AlternatePadding() {
148     BaseEncoding enc = base64().withPadChar('~');
149     testEncodingWithSeparators(enc, "", "");
150     testEncodingWithSeparators(enc, "f", "Zg~~");
151     testEncodingWithSeparators(enc, "fo", "Zm8~");
152     testEncodingWithSeparators(enc, "foo", "Zm9v");
153     testEncodingWithSeparators(enc, "foob", "Zm9vYg~~");
154     testEncodingWithSeparators(enc, "fooba", "Zm9vYmE~");
155     testEncodingWithSeparators(enc, "foobar", "Zm9vYmFy");
156   }
157 
158   @GwtIncompatible // Reader/Writer
testBase64StreamingAlternatePadding()159   public void testBase64StreamingAlternatePadding() throws IOException {
160     BaseEncoding enc = base64().withPadChar('~');
161     testStreamingEncodingWithSeparators(enc, "", "");
162     testStreamingEncodingWithSeparators(enc, "f", "Zg~~");
163     testStreamingEncodingWithSeparators(enc, "fo", "Zm8~");
164     testStreamingEncodingWithSeparators(enc, "foo", "Zm9v");
165     testStreamingEncodingWithSeparators(enc, "foob", "Zm9vYg~~");
166     testStreamingEncodingWithSeparators(enc, "fooba", "Zm9vYmE~");
167     testStreamingEncodingWithSeparators(enc, "foobar", "Zm9vYmFy");
168   }
169 
testBase64OmitPadding()170   public void testBase64OmitPadding() {
171     BaseEncoding enc = base64().omitPadding();
172     testEncodingWithSeparators(enc, "", "");
173     testEncodingWithSeparators(enc, "f", "Zg");
174     testEncodingWithSeparators(enc, "fo", "Zm8");
175     testEncodingWithSeparators(enc, "foo", "Zm9v");
176     testEncodingWithSeparators(enc, "foob", "Zm9vYg");
177     testEncodingWithSeparators(enc, "fooba", "Zm9vYmE");
178     testEncodingWithSeparators(enc, "foobar", "Zm9vYmFy");
179   }
180 
181   @GwtIncompatible // Reader/Writer
testBase64StreamingOmitPadding()182   public void testBase64StreamingOmitPadding() throws IOException {
183     BaseEncoding enc = base64().omitPadding();
184     testStreamingEncodingWithSeparators(enc, "", "");
185     testStreamingEncodingWithSeparators(enc, "f", "Zg");
186     testStreamingEncodingWithSeparators(enc, "fo", "Zm8");
187     testStreamingEncodingWithSeparators(enc, "foo", "Zm9v");
188     testStreamingEncodingWithSeparators(enc, "foob", "Zm9vYg");
189     testStreamingEncodingWithSeparators(enc, "fooba", "Zm9vYmE");
190     testStreamingEncodingWithSeparators(enc, "foobar", "Zm9vYmFy");
191   }
192 
testBase64Offset()193   public void testBase64Offset() {
194     testEncodesWithOffset(base64(), "foobar", 0, 6, "Zm9vYmFy");
195     testEncodesWithOffset(base64(), "foobar", 1, 5, "b29iYXI=");
196     testEncodesWithOffset(base64(), "foobar", 2, 3, "b2Jh");
197     testEncodesWithOffset(base64(), "foobar", 3, 1, "Yg==");
198     testEncodesWithOffset(base64(), "foobar", 4, 0, "");
199   }
200 
testBase64Url()201   public void testBase64Url() {
202     testDecodesByBytes(base64Url(), "_zzz", new byte[] {-1, 60, -13});
203     testDecodesByBytes(base64Url(), "-zzz", new byte[] {-5, 60, -13});
204   }
205 
testBase64UrlInvalidDecodings()206   public void testBase64UrlInvalidDecodings() {
207     assertFailsToDecode(base64Url(), "+zzz", "Unrecognized character: +");
208     assertFailsToDecode(base64Url(), "/zzz", "Unrecognized character: /");
209   }
210 
testBase32()211   public void testBase32() {
212     // The following test vectors are specified in RFC 4648 itself
213     testEncodingWithCasing(base32(), "", "");
214     testEncodingWithCasing(base32(), "f", "MY======");
215     testEncodingWithCasing(base32(), "fo", "MZXQ====");
216     testEncodingWithCasing(base32(), "foo", "MZXW6===");
217     testEncodingWithCasing(base32(), "foob", "MZXW6YQ=");
218     testEncodingWithCasing(base32(), "fooba", "MZXW6YTB");
219     testEncodingWithCasing(base32(), "foobar", "MZXW6YTBOI======");
220   }
221 
222   @GwtIncompatible // Reader/Writer
testBase32Streaming()223   public void testBase32Streaming() throws IOException {
224     // The following test vectors are specified in RFC 4648 itself
225     testStreamingEncodingWithCasing(base32(), "", "");
226     testStreamingEncodingWithCasing(base32(), "f", "MY======");
227     testStreamingEncodingWithCasing(base32(), "fo", "MZXQ====");
228     testStreamingEncodingWithCasing(base32(), "foo", "MZXW6===");
229     testStreamingEncodingWithCasing(base32(), "foob", "MZXW6YQ=");
230     testStreamingEncodingWithCasing(base32(), "fooba", "MZXW6YTB");
231     testStreamingEncodingWithCasing(base32(), "foobar", "MZXW6YTBOI======");
232   }
233 
testBase32LenientPadding()234   public void testBase32LenientPadding() {
235     testDecodes(base32(), "MZXW6", "foo");
236     testDecodes(base32(), "MZXW6=", "foo");
237     testDecodes(base32(), "MZXW6==", "foo");
238     testDecodes(base32(), "MZXW6===", "foo"); // proper padding length
239     testDecodes(base32(), "MZXW6====", "foo");
240     testDecodes(base32(), "MZXW6=====", "foo");
241   }
242 
testBase32AlternatePadding()243   public void testBase32AlternatePadding() {
244     BaseEncoding enc = base32().withPadChar('~');
245     testEncodingWithCasing(enc, "", "");
246     testEncodingWithCasing(enc, "f", "MY~~~~~~");
247     testEncodingWithCasing(enc, "fo", "MZXQ~~~~");
248     testEncodingWithCasing(enc, "foo", "MZXW6~~~");
249     testEncodingWithCasing(enc, "foob", "MZXW6YQ~");
250     testEncodingWithCasing(enc, "fooba", "MZXW6YTB");
251     testEncodingWithCasing(enc, "foobar", "MZXW6YTBOI~~~~~~");
252   }
253 
testBase32InvalidDecodings()254   public void testBase32InvalidDecodings() {
255     // These contain bytes not in the decodabet.
256     assertFailsToDecode(base32(), "A ", "Unrecognized character: 0x20");
257     assertFailsToDecode(base32(), "Wf2!", "Unrecognized character: f");
258     // This sentence just isn't base32() encoded.
259     assertFailsToDecode(base32(), "let's not talk of love or chains!");
260     // An 8n+{1,3,6} length string is never legal base32.
261     assertFailsToDecode(base32(), "A", "Invalid input length 1");
262     assertFailsToDecode(base32(), "ABC");
263     assertFailsToDecode(base32(), "ABCDEF");
264     // These have a combination of invalid length, unrecognized characters and wrong padding.
265     assertFailsToDecode(base32(), "AB=C", "Unrecognized character: =");
266     assertFailsToDecode(base32(), "A=BCDE", "Invalid input length 6");
267     assertFailsToDecode(base32(), "?", "Invalid input length 1");
268   }
269 
testBase32UpperCaseIsNoOp()270   public void testBase32UpperCaseIsNoOp() {
271     assertThat(base32().upperCase()).isSameInstanceAs(base32());
272   }
273 
testBase32LowerCase()274   public void testBase32LowerCase() {
275     testEncodingWithCasing(base32().lowerCase(), "foobar", "mzxw6ytboi======");
276   }
277 
testBase32IgnoreCase()278   public void testBase32IgnoreCase() {
279     BaseEncoding ignoreCase = base32().ignoreCase();
280     assertThat(ignoreCase).isNotSameInstanceAs(base32());
281     assertThat(ignoreCase).isSameInstanceAs(base32().ignoreCase());
282     testDecodes(ignoreCase, "MZXW6YTBOI======", "foobar");
283     testDecodes(ignoreCase, "mzxw6ytboi======", "foobar");
284   }
285 
testBase32Offset()286   public void testBase32Offset() {
287     testEncodesWithOffset(base32(), "foobar", 0, 6, "MZXW6YTBOI======");
288     testEncodesWithOffset(base32(), "foobar", 1, 5, "N5XWEYLS");
289     testEncodesWithOffset(base32(), "foobar", 2, 3, "N5RGC===");
290     testEncodesWithOffset(base32(), "foobar", 3, 1, "MI======");
291     testEncodesWithOffset(base32(), "foobar", 4, 0, "");
292   }
293 
testBase32Hex()294   public void testBase32Hex() {
295     // The following test vectors are specified in RFC 4648 itself
296     testEncodingWithCasing(base32Hex(), "", "");
297     testEncodingWithCasing(base32Hex(), "f", "CO======");
298     testEncodingWithCasing(base32Hex(), "fo", "CPNG====");
299     testEncodingWithCasing(base32Hex(), "foo", "CPNMU===");
300     testEncodingWithCasing(base32Hex(), "foob", "CPNMUOG=");
301     testEncodingWithCasing(base32Hex(), "fooba", "CPNMUOJ1");
302     testEncodingWithCasing(base32Hex(), "foobar", "CPNMUOJ1E8======");
303   }
304 
305   @GwtIncompatible // Reader/Writer
testBase32HexStreaming()306   public void testBase32HexStreaming() throws IOException {
307     // The following test vectors are specified in RFC 4648 itself
308     testStreamingEncodingWithCasing(base32Hex(), "", "");
309     testStreamingEncodingWithCasing(base32Hex(), "f", "CO======");
310     testStreamingEncodingWithCasing(base32Hex(), "fo", "CPNG====");
311     testStreamingEncodingWithCasing(base32Hex(), "foo", "CPNMU===");
312     testStreamingEncodingWithCasing(base32Hex(), "foob", "CPNMUOG=");
313     testStreamingEncodingWithCasing(base32Hex(), "fooba", "CPNMUOJ1");
314     testStreamingEncodingWithCasing(base32Hex(), "foobar", "CPNMUOJ1E8======");
315   }
316 
testBase32HexLenientPadding()317   public void testBase32HexLenientPadding() {
318     testDecodes(base32Hex(), "CPNMU", "foo");
319     testDecodes(base32Hex(), "CPNMU=", "foo");
320     testDecodes(base32Hex(), "CPNMU==", "foo");
321     testDecodes(base32Hex(), "CPNMU===", "foo"); // proper padding length
322     testDecodes(base32Hex(), "CPNMU====", "foo");
323     testDecodes(base32Hex(), "CPNMU=====", "foo");
324   }
325 
testBase32HexInvalidDecodings()326   public void testBase32HexInvalidDecodings() {
327     // These contain bytes not in the decodabet.
328     assertFailsToDecode(base32Hex(), "A\u007f", "Unrecognized character: 0x7f");
329     assertFailsToDecode(base32Hex(), "Wf2!", "Unrecognized character: W");
330     // This sentence just isn't base32 encoded.
331     assertFailsToDecode(base32Hex(), "let's not talk of love or chains!");
332     // An 8n+{1,3,6} length string is never legal base32.
333     assertFailsToDecode(base32Hex(), "A");
334     assertFailsToDecode(base32Hex(), "ABC");
335     assertFailsToDecode(base32Hex(), "ABCDEF");
336   }
337 
testBase32HexUpperCaseIsNoOp()338   public void testBase32HexUpperCaseIsNoOp() {
339     assertThat(base32Hex().upperCase()).isSameInstanceAs(base32Hex());
340   }
341 
testBase16()342   public void testBase16() {
343     testEncodingWithCasing(base16(), "", "");
344     testEncodingWithCasing(base16(), "f", "66");
345     testEncodingWithCasing(base16(), "fo", "666F");
346     testEncodingWithCasing(base16(), "foo", "666F6F");
347     testEncodingWithCasing(base16(), "foob", "666F6F62");
348     testEncodingWithCasing(base16(), "fooba", "666F6F6261");
349     testEncodingWithCasing(base16(), "foobar", "666F6F626172");
350   }
351 
testBase16UpperCaseIsNoOp()352   public void testBase16UpperCaseIsNoOp() {
353     assertThat(base16().upperCase()).isSameInstanceAs(base16());
354   }
355 
testBase16LowerCase()356   public void testBase16LowerCase() {
357     BaseEncoding lowerCase = base16().lowerCase();
358     assertThat(lowerCase).isNotSameInstanceAs(base16());
359     assertThat(lowerCase).isSameInstanceAs(base16().lowerCase());
360     testEncodingWithCasing(lowerCase, "foobar", "666f6f626172");
361   }
362 
testBase16IgnoreCase()363   public void testBase16IgnoreCase() {
364     BaseEncoding ignoreCase = base16().ignoreCase();
365     assertThat(ignoreCase).isNotSameInstanceAs(base16());
366     assertThat(ignoreCase).isSameInstanceAs(base16().ignoreCase());
367     testEncodingWithCasing(ignoreCase, "foobar", "666F6F626172");
368     testDecodes(ignoreCase, "666F6F626172", "foobar");
369     testDecodes(ignoreCase, "666f6f626172", "foobar");
370     testDecodes(ignoreCase, "666F6f626172", "foobar");
371   }
372 
testBase16LowerCaseIgnoreCase()373   public void testBase16LowerCaseIgnoreCase() {
374     BaseEncoding ignoreCase = base16().lowerCase().ignoreCase();
375     assertThat(ignoreCase).isNotSameInstanceAs(base16());
376     assertThat(ignoreCase).isSameInstanceAs(base16().lowerCase().ignoreCase());
377     testEncodingWithCasing(ignoreCase, "foobar", "666f6f626172");
378     testDecodes(ignoreCase, "666F6F626172", "foobar");
379     testDecodes(ignoreCase, "666f6f626172", "foobar");
380     testDecodes(ignoreCase, "666F6f626172", "foobar");
381   }
382 
383   // order the methods are called should not matter
testBase16IgnoreCaseLowerCase()384   public void testBase16IgnoreCaseLowerCase() {
385     BaseEncoding ignoreCase = base16().ignoreCase().lowerCase();
386     assertThat(ignoreCase).isNotSameInstanceAs(base16());
387     testEncodingWithCasing(ignoreCase, "foobar", "666f6f626172");
388     testDecodes(ignoreCase, "666F6F626172", "foobar");
389     testDecodes(ignoreCase, "666f6f626172", "foobar");
390     testDecodes(ignoreCase, "666F6f626172", "foobar");
391   }
392 
testBase16InvalidDecodings()393   public void testBase16InvalidDecodings() {
394     // These contain bytes not in the decodabet.
395     assertFailsToDecode(base16(), "\n\n", "Unrecognized character: 0xa");
396     assertFailsToDecode(base16(), "EFGH", "Unrecognized character: G");
397     // Valid base16 strings always have an even length.
398     assertFailsToDecode(base16(), "A", "Invalid input length 1");
399     assertFailsToDecode(base16(), "ABC");
400     // These have a combination of invalid length and unrecognized characters.
401     assertFailsToDecode(base16(), "?", "Invalid input length 1");
402     assertFailsToDecode(base16(), "ab");
403     assertFailsToDecode(base16().lowerCase(), "AB");
404   }
405 
testBase16Offset()406   public void testBase16Offset() {
407     testEncodesWithOffset(base16(), "foobar", 0, 6, "666F6F626172");
408     testEncodesWithOffset(base16(), "foobar", 1, 5, "6F6F626172");
409     testEncodesWithOffset(base16(), "foobar", 2, 3, "6F6261");
410     testEncodesWithOffset(base16(), "foobar", 3, 1, "62");
411     testEncodesWithOffset(base16(), "foobar", 4, 0, "");
412   }
413 
testEncodingWithCasing( BaseEncoding encoding, String decoded, String encoded)414   private static void testEncodingWithCasing(
415       BaseEncoding encoding, String decoded, String encoded) {
416     testEncodingWithSeparators(encoding, decoded, encoded);
417     testEncodingWithSeparators(encoding.upperCase(), decoded, Ascii.toUpperCase(encoded));
418     testEncodingWithSeparators(encoding.lowerCase(), decoded, Ascii.toLowerCase(encoded));
419   }
420 
testEncodingWithSeparators( BaseEncoding encoding, String decoded, String encoded)421   private static void testEncodingWithSeparators(
422       BaseEncoding encoding, String decoded, String encoded) {
423     testEncoding(encoding, decoded, encoded);
424 
425     // test separators work
426     for (int sepLength = 3; sepLength <= 5; sepLength++) {
427       for (String separator : ImmutableList.of(",", "\n", ";;", "")) {
428         testEncoding(
429             encoding.withSeparator(separator, sepLength),
430             decoded,
431             Joiner.on(separator).join(Splitter.fixedLength(sepLength).split(encoded)));
432       }
433     }
434   }
435 
testEncoding(BaseEncoding encoding, String decoded, String encoded)436   private static void testEncoding(BaseEncoding encoding, String decoded, String encoded) {
437     testEncodes(encoding, decoded, encoded);
438     testDecodes(encoding, encoded, decoded);
439   }
440 
testEncodes(BaseEncoding encoding, String decoded, String encoded)441   private static void testEncodes(BaseEncoding encoding, String decoded, String encoded) {
442     assertThat(encoding.encode(decoded.getBytes(UTF_8))).isEqualTo(encoded);
443   }
444 
testEncodesWithOffset( BaseEncoding encoding, String decoded, int offset, int len, String encoded)445   private static void testEncodesWithOffset(
446       BaseEncoding encoding, String decoded, int offset, int len, String encoded) {
447     assertThat(encoding.encode(decoded.getBytes(UTF_8), offset, len)).isEqualTo(encoded);
448   }
449 
testDecodes(BaseEncoding encoding, String encoded, String decoded)450   private static void testDecodes(BaseEncoding encoding, String encoded, String decoded) {
451     assertThat(encoding.canDecode(encoded)).isTrue();
452     assertThat(encoding.decode(encoded)).isEqualTo(decoded.getBytes(UTF_8));
453   }
454 
testDecodesByBytes(BaseEncoding encoding, String encoded, byte[] decoded)455   private static void testDecodesByBytes(BaseEncoding encoding, String encoded, byte[] decoded) {
456     assertThat(encoding.canDecode(encoded)).isTrue();
457     assertThat(encoding.decode(encoded)).isEqualTo(decoded);
458   }
459 
assertFailsToDecode(BaseEncoding encoding, String cannotDecode)460   private static void assertFailsToDecode(BaseEncoding encoding, String cannotDecode) {
461     assertFailsToDecode(encoding, cannotDecode, null);
462   }
463 
assertFailsToDecode( BaseEncoding encoding, String cannotDecode, @Nullable String expectedMessage)464   private static void assertFailsToDecode(
465       BaseEncoding encoding, String cannotDecode, @Nullable String expectedMessage) {
466     // We use this somewhat weird pattern with an enum for each assertion we want to make as a way
467     // of dealing with the fact that one of the assertions is @GwtIncompatible but we don't want to
468     // have to have duplicate @GwtIncompatible test methods just to make that assertion.
469     for (AssertFailsToDecodeStrategy strategy : AssertFailsToDecodeStrategy.values()) {
470       strategy.assertFailsToDecode(encoding, cannotDecode, expectedMessage);
471     }
472   }
473 
474   enum AssertFailsToDecodeStrategy {
475     @GwtIncompatible // decodingStream(Reader)
476     DECODING_STREAM {
477       @Override
assertFailsToDecode( BaseEncoding encoding, String cannotDecode, @Nullable String expectedMessage)478       void assertFailsToDecode(
479           BaseEncoding encoding, String cannotDecode, @Nullable String expectedMessage) {
480         // Regression test for case where DecodingException was swallowed by default implementation
481         // of
482         // InputStream.read(byte[], int, int)
483         // See https://github.com/google/guava/issues/3542
484         Reader reader = new StringReader(cannotDecode);
485         InputStream decodingStream = encoding.decodingStream(reader);
486         try {
487           ByteStreams.exhaust(decodingStream);
488           fail("Expected DecodingException");
489         } catch (DecodingException expected) {
490           // Don't assert on the expectedMessage; the messages for exceptions thrown from the
491           // decoding stream may differ from the messages for the decode methods.
492         } catch (IOException e) {
493           fail("Expected DecodingException but got: " + e);
494         }
495       }
496     },
497     CAN_DECODE {
498       @Override
assertFailsToDecode( BaseEncoding encoding, String cannotDecode, @Nullable String expectedMessage)499       void assertFailsToDecode(
500           BaseEncoding encoding, String cannotDecode, @Nullable String expectedMessage) {
501         assertThat(encoding.canDecode(cannotDecode)).isFalse();
502       }
503     },
504     DECODE {
505       @Override
assertFailsToDecode( BaseEncoding encoding, String cannotDecode, @Nullable String expectedMessage)506       void assertFailsToDecode(
507           BaseEncoding encoding, String cannotDecode, @Nullable String expectedMessage) {
508         try {
509           encoding.decode(cannotDecode);
510           fail("Expected IllegalArgumentException");
511         } catch (IllegalArgumentException expected) {
512           if (expectedMessage != null) {
513             assertThat(expected).hasCauseThat().hasMessageThat().isEqualTo(expectedMessage);
514           }
515         }
516       }
517     },
518     DECODE_CHECKED {
519       @Override
assertFailsToDecode( BaseEncoding encoding, String cannotDecode, @Nullable String expectedMessage)520       void assertFailsToDecode(
521           BaseEncoding encoding, String cannotDecode, @Nullable String expectedMessage) {
522         try {
523           encoding.decodeChecked(cannotDecode);
524           fail("Expected DecodingException");
525         } catch (DecodingException expected) {
526           if (expectedMessage != null) {
527             assertThat(expected).hasMessageThat().isEqualTo(expectedMessage);
528           }
529         }
530       }
531     };
532 
assertFailsToDecode( BaseEncoding encoding, String cannotDecode, @Nullable String expectedMessage)533     abstract void assertFailsToDecode(
534         BaseEncoding encoding, String cannotDecode, @Nullable String expectedMessage);
535   }
536 
537   @GwtIncompatible // Reader/Writer
testStreamingEncodingWithCasing( BaseEncoding encoding, String decoded, String encoded)538   private static void testStreamingEncodingWithCasing(
539       BaseEncoding encoding, String decoded, String encoded) throws IOException {
540     testStreamingEncodingWithSeparators(encoding, decoded, encoded);
541     testStreamingEncodingWithSeparators(encoding.upperCase(), decoded, Ascii.toUpperCase(encoded));
542     testStreamingEncodingWithSeparators(encoding.lowerCase(), decoded, Ascii.toLowerCase(encoded));
543   }
544 
545   @GwtIncompatible // Reader/Writer
testStreamingEncodingWithSeparators( BaseEncoding encoding, String decoded, String encoded)546   private static void testStreamingEncodingWithSeparators(
547       BaseEncoding encoding, String decoded, String encoded) throws IOException {
548     testStreamingEncoding(encoding, decoded, encoded);
549 
550     // test separators work
551     for (int sepLength = 3; sepLength <= 5; sepLength++) {
552       for (String separator : ImmutableList.of(",", "\n", ";;", "")) {
553         testStreamingEncoding(
554             encoding.withSeparator(separator, sepLength),
555             decoded,
556             Joiner.on(separator).join(Splitter.fixedLength(sepLength).split(encoded)));
557       }
558     }
559   }
560 
561   @GwtIncompatible // Reader/Writer
testStreamingEncoding(BaseEncoding encoding, String decoded, String encoded)562   private static void testStreamingEncoding(BaseEncoding encoding, String decoded, String encoded)
563       throws IOException {
564     testStreamingEncodes(encoding, decoded, encoded);
565     testStreamingDecodes(encoding, encoded, decoded);
566   }
567 
568   @GwtIncompatible // Writer
testStreamingEncodes(BaseEncoding encoding, String decoded, String encoded)569   private static void testStreamingEncodes(BaseEncoding encoding, String decoded, String encoded)
570       throws IOException {
571     StringWriter writer = new StringWriter();
572     try (OutputStream encodingStream = encoding.encodingStream(writer)) {
573       encodingStream.write(decoded.getBytes(UTF_8));
574     }
575     assertThat(writer.toString()).isEqualTo(encoded);
576   }
577 
578   @GwtIncompatible // Reader
testStreamingDecodes(BaseEncoding encoding, String encoded, String decoded)579   private static void testStreamingDecodes(BaseEncoding encoding, String encoded, String decoded)
580       throws IOException {
581     byte[] bytes = decoded.getBytes(UTF_8);
582     try (InputStream decodingStream = encoding.decodingStream(new StringReader(encoded))) {
583       for (int i = 0; i < bytes.length; i++) {
584         assertThat(decodingStream.read()).isEqualTo(bytes[i] & 0xFF);
585       }
586       assertThat(decodingStream.read()).isEqualTo(-1);
587     }
588   }
589 
testToString()590   public void testToString() {
591     assertThat(base64().toString()).isEqualTo("BaseEncoding.base64().withPadChar('=')");
592     assertThat(base32Hex().omitPadding().toString())
593         .isEqualTo("BaseEncoding.base32Hex().omitPadding()");
594     assertThat(base32().lowerCase().withPadChar('$').toString())
595         .isEqualTo("BaseEncoding.base32().lowerCase().withPadChar('$')");
596     assertThat(base16().withSeparator("\n", 10).toString())
597         .isEqualTo("BaseEncoding.base16().withSeparator(\"\n\", 10)");
598   }
599 }
600