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 presumable free memory; an estimate of the amount of memory that could be allocated.
95       *
96       * <p>This performs a garbage clean-up and the obtains the presumed amount of free memory
97       * that can be allocated in this VM. This is computed as:<p>
98       *
99       * <pre>
100      * System.gc();
101      * long allocatedMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
102      * long presumableFreeMemory = Runtime.getRuntime().maxMemory() - allocatedMemory;
103      * </pre>
104      *
105      * @return the presumable free memory
106      * @see <a href="https://stackoverflow.com/a/18366283">
107      *     Christian Fries StackOverflow answer on Java available memory</a>
108      */
109     static long getPresumableFreeMemory() {
110         System.gc();
111         final long allocatedMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
112         return Runtime.getRuntime().maxMemory() - allocatedMemory;
113     }
114 
115     BaseNCodec codec;
116 
117     @BeforeEach
118     void setUp() {
119         codec = new BaseNCodec(0, 0, 0, 0) {
120             @Override
121             void decode(final byte[] array, final int i, final int length, final Context context) {
122             }
123 
124             @Override
125             void encode(final byte[] array, final int i, final int length, final Context context) {
126             }
127 
128             @Override
129             protected boolean isInAlphabet(final byte b) {
130                 return b == 'O' || b == 'K'; // allow OK
131             }
132         };
133     }
134 
135     @Test
136     void testBaseNCodec() {
137         assertNotNull(codec);
138     }
139 
140     @Test
141     void testContainsAlphabetOrPad() {
142         assertFalse(codec.containsAlphabetOrPad(null));
143         assertFalse(codec.containsAlphabetOrPad(new byte[]{}));
144         assertTrue(codec.containsAlphabetOrPad("OK".getBytes()));
145         assertTrue(codec.containsAlphabetOrPad("OK ".getBytes()));
146         assertFalse(codec.containsAlphabetOrPad("ok ".getBytes()));
147         assertTrue(codec.containsAlphabetOrPad(new byte[]{codec.pad}));
148     }
149 
150     /**
151      * Test the Context string representation has debugging info.
152      * This is not part of the API and the test should be changed if the string
153      * format is updated.
154      */
155     @Test
156     void testContextToString() {
157         final Context context = new Context();
158         context.buffer = new byte[3];
159         context.currentLinePos = 13;
160         context.eof = true;
161         context.ibitWorkArea = 777;
162         context.lbitWorkArea = 999;
163         context.modulus = 5;
164         context.pos = 42;
165         context.readPos = 981;
166         final String text = context.toString();
167         assertTrue(text.contains("[0, 0, 0]"));
168         assertTrue(text.contains("13"));
169         assertTrue(text.contains("true"));
170         assertTrue(text.contains("777"));
171         assertTrue(text.contains("999"));
172         assertTrue(text.contains("5"));
173         assertTrue(text.contains("42"));
174         assertTrue(text.contains("981"));
175     }
176 
177 //    @Test
178 //    void testGetEncodedLength() {
179 //        fail("Not yet implemented");
180 //    }
181 
182     @Test
183     void testEnsureBufferSize() {
184         final BaseNCodec ncodec = new NoOpBaseNCodec();
185         final Context context = new Context();
186         assertNull(context.buffer, "Initial buffer should be null");
187 
188         // Test initialization
189         context.pos = 76979;
190         context.readPos = 273;
191         ncodec.ensureBufferSize(0, context);
192         assertNotNull(context.buffer, "buffer should be initialized");
193         assertEquals(ncodec.getDefaultBufferSize(), context.buffer.length, "buffer should be initialized to default size");
194         assertEquals(0, context.pos, "context position");
195         assertEquals(0, context.readPos, "context read position");
196 
197         // Test when no expansion is required
198         ncodec.ensureBufferSize(1, context);
199         assertEquals(ncodec.getDefaultBufferSize(), context.buffer.length, "buffer should not expand unless required");
200 
201         // Test expansion
202         int length = context.buffer.length;
203         context.pos = length;
204         int extra = 1;
205         ncodec.ensureBufferSize(extra, context);
206         assertTrue(context.buffer.length >= length + extra, "buffer should expand");
207 
208         // Test expansion beyond double the buffer size.
209         // Hits the edge case where the required capacity is more than the default expansion.
210         length = context.buffer.length;
211         context.pos = length;
212         extra = length * 10;
213         ncodec.ensureBufferSize(extra, context);
214         assertTrue(context.buffer.length >= length + extra, "buffer should expand beyond double capacity");
215     }
216 
217     /**
218      * Test to expand to beyond the max buffer size.
219      *
220      * <p>Note: If the buffer is required to expand to above the max buffer size it may not work
221      * on all VMs and may have to be annotated with @Ignore.</p>
222      */
223     @Test
224     void testEnsureBufferSizeExpandsToBeyondMaxBufferSize() {
225         assertEnsureBufferSizeExpandsToMaxBufferSize(true);
226     }
227 
228     /**
229      * Test to expand to the max buffer size.
230      */
231     @Test
232     void testEnsureBufferSizeExpandsToMaxBufferSize() {
233         assertEnsureBufferSizeExpandsToMaxBufferSize(false);
234     }
235 
236     @Test
237     void testEnsureBufferSizeThrowsOnOverflow() {
238         final BaseNCodec ncodec = new NoOpBaseNCodec();
239         final Context context = new Context();
240 
241         final int length = 10;
242         context.buffer = new byte[length];
243         context.pos = length;
244         final int extra = Integer.MAX_VALUE;
245         assertThrows(OutOfMemoryError.class, () -> ncodec.ensureBufferSize(extra, context));
246     }
247 
248     //
249 //    @Test
250 //    void testEncodeObject() {
251 //        fail("Not yet implemented");
252 //    }
253 //
254 //    @Test
255 //    void testEncodeToString() {
256 //        fail("Not yet implemented");
257 //    }
258 //
259 //    @Test
260 //    void testDecodeObject() {
261 //        fail("Not yet implemented");
262 //    }
263 //
264 //    @Test
265 //    void testDecodeString() {
266 //        fail("Not yet implemented");
267 //    }
268 //
269 //    @Test
270 //    void testDecodeByteArray() {
271 //        fail("Not yet implemented");
272 //    }
273 //
274 //    @Test
275 //    void testEncodeByteArray() {
276 //        fail("Not yet implemented");
277 //    }
278 //
279 //    @Test
280 //    void testEncodeAsString() {
281 //        fail("Not yet implemented");
282 //    }
283 //
284 //    @Test
285 //    void testEncodeByteArrayIntInt() {
286 //        fail("Not yet implemented");
287 //    }
288 //
289 //    @Test
290 //    void testDecodeByteArrayIntInt() {
291 //        fail("Not yet implemented");
292 //    }
293 //
294     @Test
295     void testIsInAlphabetByte() {
296         assertFalse(codec.isInAlphabet((byte) 0));
297         assertFalse(codec.isInAlphabet((byte) 'a'));
298         assertTrue(codec.isInAlphabet((byte) 'O'));
299         assertTrue(codec.isInAlphabet((byte) 'K'));
300     }
301 
302     @Test
303     void testIsInAlphabetByteArrayBoolean() {
304         assertTrue(codec.isInAlphabet(new byte[] {}, false));
305         assertTrue(codec.isInAlphabet(new byte[] { 'O' }, false));
306         assertFalse(codec.isInAlphabet(new byte[] { 'O', ' ' }, false));
307         assertFalse(codec.isInAlphabet(new byte[] { ' ' }, false));
308         assertTrue(codec.isInAlphabet(new byte[] {}, true));
309         assertTrue(codec.isInAlphabet(new byte[] { 'O' }, true));
310         assertTrue(codec.isInAlphabet(new byte[] { 'O', ' ' }, true));
311         assertTrue(codec.isInAlphabet(new byte[] { ' ' }, true));
312     }
313 
314     @Test
315     void testIsInAlphabetString() {
316         assertTrue(codec.isInAlphabet("OK"));
317         assertTrue(codec.isInAlphabet("O=K= \t\n\r"));
318     }
319 
320     //    @Test
321 //    void testHasData() {
322 //        fail("Not yet implemented");
323 //    }
324 //
325 //    @Test
326 //    void testAvail() {
327 //        fail("Not yet implemented");
328 //    }
329 //
330 //    @Test
331 //    void testEnsureBufferSize() {
332 //        fail("Not yet implemented");
333 //    }
334 //
335 //    @Test
336 //    void testReadResults() {
337 //        fail("Not yet implemented");
338 //    }
339 //
340     @Test
341     void testIsWhiteSpace() {
342         assertTrue(Character.isWhitespace((byte) ' '));
343         assertTrue(Character.isWhitespace((byte) '\n'));
344         assertTrue(Character.isWhitespace((byte) '\r'));
345         assertTrue(Character.isWhitespace((byte) '\t'));
346         assertTrue(Character.isWhitespace((byte) '\f'));
347         assertTrue(Character.isWhitespace((byte) '\u000B'));
348         for (byte b = Byte.MIN_VALUE; b < Byte.MAX_VALUE; b++) {
349             final byte byteToCheck = b;
350             assertEquals(Character.isWhitespace(b), Character.isWhitespace(byteToCheck));
351         }
352     }
353 
354     @Test
355     void testProvidePaddingByte() {
356         // Given
357         codec = new BaseNCodec(0, 0, 0, 0, (byte) 0x25) {
358             @Override
359             void decode(final byte[] array, final int i, final int length, final Context context) {
360                 // no-op
361             }
362 
363             @Override
364             void encode(final byte[] array, final int i, final int length, final Context context) {
365                 // no-op
366             }
367 
368             @Override
369             protected boolean isInAlphabet(final byte b) {
370                 return b == 'O' || b == 'K'; // allow OK
371             }
372         };
373 
374         // When
375         final byte actualPaddingByte = codec.pad;
376 
377         // Then
378         assertEquals(0x25, actualPaddingByte);
379     }
380 }