View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.codec.binary;
19  
20  import static org.junit.jupiter.api.Assertions.assertEquals;
21  import static org.junit.jupiter.api.Assertions.assertFalse;
22  import static org.junit.jupiter.api.Assertions.assertNotNull;
23  import static org.junit.jupiter.api.Assertions.assertNull;
24  import static org.junit.jupiter.api.Assertions.assertThrows;
25  import static org.junit.jupiter.api.Assertions.assertTrue;
26  import static org.junit.jupiter.api.Assumptions.assumeTrue;
27  
28  import org.apache.commons.codec.binary.BaseNCodec.Context;
29  import org.junit.jupiter.api.BeforeEach;
30  import org.junit.jupiter.api.Test;
31  
32  class BaseNCodecTest {
33  
34      private static void assertEnsureBufferSizeExpandsToMaxBufferSize(final boolean exceedMaxBufferSize) {
35          // This test is memory hungry.
36          // By default expansion will double the buffer size.
37          // Using a buffer that must be doubled to get close to 2GiB requires at least 3GiB
38          // of memory for the test (1GiB existing + 2GiB new).
39          // As a compromise we use an empty buffer and rely on the expansion switching
40          // to the minimum required capacity if doubling is not enough.
41  
42          // To effectively use a full buffer of ~1GiB change the following for: 1 << 30.
43          // Setting to zero has the lowest memory footprint for this test.
44          final int length = 0;
45  
46          final long presumableFreeMemory = getPresumableFreeMemory();
47          // 2GiB + 32 KiB + length
48          // 2GiB: Buffer to allocate
49          // 32KiB: Some headroom
50          // length: Existing buffer
51          final long estimatedMemory = (1L << 31) + 32 * 1024 + length;
52          assumeTrue(presumableFreeMemory > estimatedMemory, "Not enough free memory for the test");
53  
54          final int max = Integer.MAX_VALUE - 8;
55  
56          // Check the conservative maximum buffer size can actually be exceeded by the VM
57          // otherwise the test is not valid.
58          if (exceedMaxBufferSize) {
59              assumeCanAllocateBufferSize(max + 1);
60              // Free-memory.
61              // This may not be necessary as the byte[] is now out of scope
62              System.gc();
63          }
64  
65          final BaseNCodec ncodec = new NoOpBaseNCodec();
66          final Context context = new Context();
67  
68          // Allocate the initial buffer
69          context.buffer = new byte[length];
70          context.pos = length;
71          // Compute the extra to reach or exceed the max buffer size
72          int extra = max - length;
73          if (exceedMaxBufferSize) {
74              extra++;
75          }
76          ncodec.ensureBufferSize(extra, context);
77          assertTrue(context.buffer.length >= length + extra);
78      }
79  
80      /**
81       * Verify this VM can allocate the given size byte array. Otherwise skip the test.
82       */
83      private static void assumeCanAllocateBufferSize(final int size) {
84          byte[] bytes = null;
85          try {
86              bytes = new byte[size];
87          } catch (final OutOfMemoryError ignore) {
88              // ignore
89          }
90          assumeTrue(bytes != null, "Cannot allocate array of size: " + size);
91      }
92  
93      /**
94       * Gets the presumed free memory; an estimate of the amount of memory that could be allocated.
95       *
96       * <p>
97       * This performs a garbage clean-up and then obtains the presumed amount of free memory
98       * that can be allocated in this VM. This is computed as:
99       * </p>
100      * <pre>
101      * System.gc();
102      * long allocatedMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
103      * long presumableFreeMemory = Runtime.getRuntime().maxMemory() - allocatedMemory;
104      * </pre>
105      *
106      * @return The presumable free memory
107      * @see <a href="https://stackoverflow.com/a/18366283">
108      *     Christian Fries StackOverflow answer on Java available memory</a>
109      */
110     static long getPresumableFreeMemory() {
111         System.gc();
112         final long allocatedMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
113         return Runtime.getRuntime().maxMemory() - allocatedMemory;
114     }
115 
116     BaseNCodec codec;
117 
118     @BeforeEach
119     void setUp() {
120         codec = new BaseNCodec(0, 0, 0, 0) {
121             @Override
122             void decode(final byte[] array, final int i, final int length, final Context context) {
123             }
124 
125             @Override
126             void encode(final byte[] array, final int i, final int length, final Context context) {
127             }
128 
129             @Override
130             protected boolean isInAlphabet(final byte b) {
131                 return b == 'O' || b == 'K'; // allow OK
132             }
133         };
134     }
135 
136     @Test
137     void testBaseNCodec() {
138         assertNotNull(codec);
139     }
140 
141     @Test
142     void testContainsAlphabetOrPad() {
143         assertFalse(codec.containsAlphabetOrPad(null));
144         assertFalse(codec.containsAlphabetOrPad(new byte[]{}));
145         assertTrue(codec.containsAlphabetOrPad("OK".getBytes()));
146         assertTrue(codec.containsAlphabetOrPad("OK ".getBytes()));
147         assertFalse(codec.containsAlphabetOrPad("ok ".getBytes()));
148         assertTrue(codec.containsAlphabetOrPad(new byte[]{codec.pad}));
149     }
150 
151     /**
152      * Test the Context string representation has debugging info.
153      * This is not part of the API and the test should be changed if the string
154      * format is updated.
155      */
156     @Test
157     void testContextToString() {
158         final Context context = new Context();
159         context.buffer = new byte[3];
160         context.currentLinePos = 13;
161         context.eof = true;
162         context.ibitWorkArea = 777;
163         context.lbitWorkArea = 999;
164         context.modulus = 5;
165         context.pos = 42;
166         context.readPos = 981;
167         final String text = context.toString();
168         assertTrue(text.contains("[0, 0, 0]"));
169         assertTrue(text.contains("13"));
170         assertTrue(text.contains("true"));
171         assertTrue(text.contains("777"));
172         assertTrue(text.contains("999"));
173         assertTrue(text.contains("5"));
174         assertTrue(text.contains("42"));
175         assertTrue(text.contains("981"));
176     }
177 
178 //    @Test
179 //    void testGetEncodedLength() {
180 //        fail("Not yet implemented");
181 //    }
182 
183     @Test
184     void testEnsureBufferSize() {
185         final BaseNCodec ncodec = new NoOpBaseNCodec();
186         final Context context = new Context();
187         assertNull(context.buffer, "Initial buffer should be null");
188 
189         // Test initialization
190         context.pos = 76979;
191         context.readPos = 273;
192         ncodec.ensureBufferSize(0, context);
193         assertNotNull(context.buffer, "buffer should be initialized");
194         assertEquals(ncodec.getDefaultBufferSize(), context.buffer.length, "buffer should be initialized to default size");
195         assertEquals(0, context.pos, "context position");
196         assertEquals(0, context.readPos, "context read position");
197 
198         // Test when no expansion is required
199         ncodec.ensureBufferSize(1, context);
200         assertEquals(ncodec.getDefaultBufferSize(), context.buffer.length, "buffer should not expand unless required");
201 
202         // Test expansion
203         int length = context.buffer.length;
204         context.pos = length;
205         int extra = 1;
206         ncodec.ensureBufferSize(extra, context);
207         assertTrue(context.buffer.length >= length + extra, "buffer should expand");
208 
209         // Test expansion beyond double the buffer size.
210         // Hits the edge case where the required capacity is more than the default expansion.
211         length = context.buffer.length;
212         context.pos = length;
213         extra = length * 10;
214         ncodec.ensureBufferSize(extra, context);
215         assertTrue(context.buffer.length >= length + extra, "buffer should expand beyond double capacity");
216     }
217 
218     /**
219      * Test to expand to beyond the max buffer size.
220      *
221      * <p>
222      * Note: If the buffer is required to expand to above the max buffer size it may not work
223      * on all VMs and may have to be annotated with @Ignore.
224      * </p>
225      */
226     @Test
227     void testEnsureBufferSizeExpandsToBeyondMaxBufferSize() {
228         assertEnsureBufferSizeExpandsToMaxBufferSize(true);
229     }
230 
231     /**
232      * Test to expand to the max buffer size.
233      */
234     @Test
235     void testEnsureBufferSizeExpandsToMaxBufferSize() {
236         assertEnsureBufferSizeExpandsToMaxBufferSize(false);
237     }
238 
239     @Test
240     void testEnsureBufferSizeThrowsOnOverflow() {
241         final BaseNCodec ncodec = new NoOpBaseNCodec();
242         final Context context = new Context();
243 
244         final int length = 10;
245         context.buffer = new byte[length];
246         context.pos = length;
247         final int extra = Integer.MAX_VALUE;
248         assertThrows(OutOfMemoryError.class, () -> ncodec.ensureBufferSize(extra, context));
249     }
250 
251     //
252 //    @Test
253 //    void testEncodeObject() {
254 //        fail("Not yet implemented");
255 //    }
256 //
257 //    @Test
258 //    void testEncodeToString() {
259 //        fail("Not yet implemented");
260 //    }
261 //
262 //    @Test
263 //    void testDecodeObject() {
264 //        fail("Not yet implemented");
265 //    }
266 //
267 //    @Test
268 //    void testDecodeString() {
269 //        fail("Not yet implemented");
270 //    }
271 //
272 //    @Test
273 //    void testDecodeByteArray() {
274 //        fail("Not yet implemented");
275 //    }
276 //
277 //    @Test
278 //    void testEncodeByteArray() {
279 //        fail("Not yet implemented");
280 //    }
281 //
282 //    @Test
283 //    void testEncodeAsString() {
284 //        fail("Not yet implemented");
285 //    }
286 //
287 //    @Test
288 //    void testEncodeByteArrayIntInt() {
289 //        fail("Not yet implemented");
290 //    }
291 //
292 //    @Test
293 //    void testDecodeByteArrayIntInt() {
294 //        fail("Not yet implemented");
295 //    }
296 //
297     @Test
298     void testIsInAlphabetByte() {
299         assertFalse(codec.isInAlphabet((byte) 0));
300         assertFalse(codec.isInAlphabet((byte) 'a'));
301         assertTrue(codec.isInAlphabet((byte) 'O'));
302         assertTrue(codec.isInAlphabet((byte) 'K'));
303     }
304 
305     @Test
306     void testIsInAlphabetByteArrayBoolean() {
307         assertTrue(codec.isInAlphabet(new byte[] {}, false));
308         assertTrue(codec.isInAlphabet(new byte[] { 'O' }, false));
309         assertFalse(codec.isInAlphabet(new byte[] { 'O', ' ' }, false));
310         assertFalse(codec.isInAlphabet(new byte[] { ' ' }, false));
311         assertTrue(codec.isInAlphabet(new byte[] {}, true));
312         assertTrue(codec.isInAlphabet(new byte[] { 'O' }, true));
313         assertTrue(codec.isInAlphabet(new byte[] { 'O', ' ' }, true));
314         assertTrue(codec.isInAlphabet(new byte[] { ' ' }, true));
315     }
316 
317     @Test
318     void testIsInAlphabetString() {
319         assertTrue(codec.isInAlphabet("OK"));
320         assertTrue(codec.isInAlphabet("O=K= \t\n\r"));
321     }
322 
323     //    @Test
324 //    void testHasData() {
325 //        fail("Not yet implemented");
326 //    }
327 //
328 //    @Test
329 //    void testAvail() {
330 //        fail("Not yet implemented");
331 //    }
332 //
333 //    @Test
334 //    void testEnsureBufferSize() {
335 //        fail("Not yet implemented");
336 //    }
337 //
338 //    @Test
339 //    void testReadResults() {
340 //        fail("Not yet implemented");
341 //    }
342 //
343     @Test
344     void testIsWhiteSpace() {
345         assertTrue(Character.isWhitespace((byte) ' '));
346         assertTrue(Character.isWhitespace((byte) '\n'));
347         assertTrue(Character.isWhitespace((byte) '\r'));
348         assertTrue(Character.isWhitespace((byte) '\t'));
349         assertTrue(Character.isWhitespace((byte) '\f'));
350         assertTrue(Character.isWhitespace((byte) '\u000B'));
351         for (byte b = Byte.MIN_VALUE; b < Byte.MAX_VALUE; b++) {
352             final byte byteToCheck = b;
353             assertEquals(Character.isWhitespace(b), Character.isWhitespace(byteToCheck));
354         }
355     }
356 
357     @Test
358     void testProvidePaddingByte() {
359         // Given
360         codec = new BaseNCodec(0, 0, 0, 0, (byte) 0x25) {
361             @Override
362             void decode(final byte[] array, final int i, final int length, final Context context) {
363                 // no-op
364             }
365 
366             @Override
367             void encode(final byte[] array, final int i, final int length, final Context context) {
368                 // no-op
369             }
370 
371             @Override
372             protected boolean isInAlphabet(final byte b) {
373                 return b == 'O' || b == 'K'; // allow OK
374             }
375         };
376 
377         // When
378         final byte actualPaddingByte = codec.pad;
379 
380         // Then
381         assertEquals(0x25, actualPaddingByte);
382     }
383 }