/* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.json; import junit.framework.AssertionFailedError; import junit.framework.TestCase; /** * This black box test was written without inspecting the non-free org.json sourcecode. */ public class JSONTokenerTest extends TestCase { public void testNulls() throws JSONException { // JSONTokener accepts null, only to fail later on almost all APIs! new JSONTokener(null).back(); try { new JSONTokener(null).more(); fail(); } catch (NullPointerException e) { } try { new JSONTokener(null).next(); fail(); } catch (NullPointerException e) { } try { new JSONTokener(null).next(3); fail(); } catch (NullPointerException e) { } try { new JSONTokener(null).next('A'); fail(); } catch (NullPointerException e) { } try { new JSONTokener(null).nextClean(); fail(); } catch (NullPointerException e) { } try { new JSONTokener(null).nextString('"'); fail(); } catch (NullPointerException e) { } try { new JSONTokener(null).nextTo('A'); fail(); } catch (NullPointerException e) { } try { new JSONTokener(null).nextTo("ABC"); fail(); } catch (NullPointerException e) { } try { new JSONTokener(null).nextValue(); fail(); } catch (NullPointerException e) { } try { new JSONTokener(null).skipPast("ABC"); fail(); } catch (NullPointerException e) { } try { new JSONTokener(null).skipTo('A'); fail(); } catch (NullPointerException e) { } assertEquals("foo! at character 0 of null", new JSONTokener(null).syntaxError("foo!").getMessage()); assertEquals(" at character 0 of null", new JSONTokener(null).toString()); } public void testEmptyString() throws JSONException { JSONTokener backTokener = new JSONTokener(""); backTokener.back(); assertEquals(" at character 0 of ", backTokener.toString()); assertFalse(new JSONTokener("").more()); assertEquals('\0', new JSONTokener("").next()); try { new JSONTokener("").next(3); fail(); } catch (JSONException expected) { } try { new JSONTokener("").next('A'); fail(); } catch (JSONException e) { } assertEquals('\0', new JSONTokener("").nextClean()); try { new JSONTokener("").nextString('"'); fail(); } catch (JSONException e) { } assertEquals("", new JSONTokener("").nextTo('A')); assertEquals("", new JSONTokener("").nextTo("ABC")); try { new JSONTokener("").nextValue(); fail(); } catch (JSONException e) { } new JSONTokener("").skipPast("ABC"); assertEquals('\0', new JSONTokener("").skipTo('A')); assertEquals("foo! at character 0 of ", new JSONTokener("").syntaxError("foo!").getMessage()); assertEquals(" at character 0 of ", new JSONTokener("").toString()); } public void testCharacterNavigation() throws JSONException { JSONTokener abcdeTokener = new JSONTokener("ABCDE"); assertEquals('A', abcdeTokener.next()); assertEquals('B', abcdeTokener.next('B')); assertEquals("CD", abcdeTokener.next(2)); try { abcdeTokener.next(2); fail(); } catch (JSONException e) { } assertEquals('E', abcdeTokener.nextClean()); assertEquals('\0', abcdeTokener.next()); assertFalse(abcdeTokener.more()); abcdeTokener.back(); assertTrue(abcdeTokener.more()); assertEquals('E', abcdeTokener.next()); } public void testBackNextAndMore() throws JSONException { JSONTokener abcTokener = new JSONTokener("ABC"); assertTrue(abcTokener.more()); abcTokener.next(); abcTokener.next(); assertTrue(abcTokener.more()); abcTokener.next(); assertFalse(abcTokener.more()); abcTokener.back(); assertTrue(abcTokener.more()); abcTokener.next(); assertFalse(abcTokener.more()); abcTokener.back(); abcTokener.back(); abcTokener.back(); abcTokener.back(); // you can back up before the beginning of a String! assertEquals('A', abcTokener.next()); } public void testNextMatching() throws JSONException { JSONTokener abcdTokener = new JSONTokener("ABCD"); assertEquals('A', abcdTokener.next('A')); try { abcdTokener.next('C'); // although it failed, this op consumes a character of input fail(); } catch (JSONException e) { } assertEquals('C', abcdTokener.next('C')); assertEquals('D', abcdTokener.next('D')); try { abcdTokener.next('E'); fail(); } catch (JSONException e) { } } public void testNextN() throws JSONException { JSONTokener abcdeTokener = new JSONTokener("ABCDEF"); assertEquals("", abcdeTokener.next(0)); try { abcdeTokener.next(7); fail(); } catch (JSONException e) { } assertEquals("ABC", abcdeTokener.next(3)); try { abcdeTokener.next(4); fail(); } catch (JSONException e) { } } public void testNextNWithAllRemaining() throws JSONException { JSONTokener tokener = new JSONTokener("ABCDEF"); tokener.next(3); try { tokener.next(3); } catch (JSONException e) { AssertionFailedError error = new AssertionFailedError("off-by-one error?"); error.initCause(e); throw error; } } public void testNext0() throws JSONException { JSONTokener tokener = new JSONTokener("ABCDEF"); tokener.next(5); tokener.next(); try { tokener.next(0); } catch (JSONException e) { Error error = new AssertionFailedError("Returning an empty string should be valid"); error.initCause(e); throw error; } } public void testNextCleanComments() throws JSONException { JSONTokener tokener = new JSONTokener( " A /*XX*/B/*XX//XX\n//XX\nXX*/C//X//X//X\nD/*X*///X\n"); assertEquals('A', tokener.nextClean()); assertEquals('B', tokener.nextClean()); assertEquals('C', tokener.nextClean()); assertEquals('D', tokener.nextClean()); assertEquals('\0', tokener.nextClean()); } public void testNextCleanNestedCStyleComments() throws JSONException { JSONTokener tokener = new JSONTokener("A /* B /* C */ D */ E"); assertEquals('A', tokener.nextClean()); assertEquals('D', tokener.nextClean()); assertEquals('*', tokener.nextClean()); assertEquals('/', tokener.nextClean()); assertEquals('E', tokener.nextClean()); } /** * Some applications rely on parsing '#' to lead an end-of-line comment. * http://b/2571423 */ public void testNextCleanHashComments() throws JSONException { JSONTokener tokener = new JSONTokener("A # B */ /* C */ \nD #"); assertEquals('A', tokener.nextClean()); assertEquals('D', tokener.nextClean()); assertEquals('\0', tokener.nextClean()); } public void testNextCleanCommentsTrailingSingleSlash() throws JSONException { JSONTokener tokener = new JSONTokener(" / S /"); assertEquals('/', tokener.nextClean()); assertEquals('S', tokener.nextClean()); assertEquals('/', tokener.nextClean()); assertEquals("nextClean doesn't consume a trailing slash", '\0', tokener.nextClean()); } public void testNextCleanTrailingOpenComment() throws JSONException { try { new JSONTokener(" /* ").nextClean(); fail(); } catch (JSONException e) { } assertEquals('\0', new JSONTokener(" // ").nextClean()); } public void testNextCleanNewlineDelimiters() throws JSONException { assertEquals('B', new JSONTokener(" // \r\n B ").nextClean()); assertEquals('B', new JSONTokener(" // \n B ").nextClean()); assertEquals('B', new JSONTokener(" // \r B ").nextClean()); } public void testNextCleanSkippedWhitespace() throws JSONException { assertEquals("character tabulation", 'A', new JSONTokener("\tA").nextClean()); assertEquals("line feed", 'A', new JSONTokener("\nA").nextClean()); assertEquals("carriage return", 'A', new JSONTokener("\rA").nextClean()); assertEquals("space", 'A', new JSONTokener(" A").nextClean()); } /** * Tests which characters tokener treats as ignorable whitespace. See Kevin Bourrillion's * <a href="https://spreadsheets.google.com/pub?key=pd8dAQyHbdewRsnE5x5GzKQ">list * of whitespace characters</a>. */ public void testNextCleanRetainedWhitespace() throws JSONException { assertNotClean("null", '\u0000'); assertNotClean("next line", '\u0085'); assertNotClean("non-breaking space", '\u00a0'); assertNotClean("ogham space mark", '\u1680'); assertNotClean("mongolian vowel separator", '\u180e'); assertNotClean("en quad", '\u2000'); assertNotClean("em quad", '\u2001'); assertNotClean("en space", '\u2002'); assertNotClean("em space", '\u2003'); assertNotClean("three-per-em space", '\u2004'); assertNotClean("four-per-em space", '\u2005'); assertNotClean("six-per-em space", '\u2006'); assertNotClean("figure space", '\u2007'); assertNotClean("punctuation space", '\u2008'); assertNotClean("thin space", '\u2009'); assertNotClean("hair space", '\u200a'); assertNotClean("zero-width space", '\u200b'); assertNotClean("left-to-right mark", '\u200e'); assertNotClean("right-to-left mark", '\u200f'); assertNotClean("line separator", '\u2028'); assertNotClean("paragraph separator", '\u2029'); assertNotClean("narrow non-breaking space", '\u202f'); assertNotClean("medium mathematical space", '\u205f'); assertNotClean("ideographic space", '\u3000'); assertNotClean("line tabulation", '\u000b'); assertNotClean("form feed", '\u000c'); assertNotClean("information separator 4", '\u001c'); assertNotClean("information separator 3", '\u001d'); assertNotClean("information separator 2", '\u001e'); assertNotClean("information separator 1", '\u001f'); } private void assertNotClean(String name, char c) throws JSONException { assertEquals("The character " + name + " is not whitespace according to the JSON spec.", c, new JSONTokener(new String(new char[] { c, 'A' })).nextClean()); } public void testNextString() throws JSONException { assertEquals("", new JSONTokener("'").nextString('\'')); assertEquals("", new JSONTokener("\"").nextString('\"')); assertEquals("ABC", new JSONTokener("ABC'DEF").nextString('\'')); assertEquals("ABC", new JSONTokener("ABC'''DEF").nextString('\'')); // nextString permits slash-escaping of arbitrary characters! assertEquals("ABC", new JSONTokener("A\\B\\C'DEF").nextString('\'')); JSONTokener tokener = new JSONTokener(" 'abc' 'def' \"ghi\""); tokener.next(); assertEquals('\'', tokener.next()); assertEquals("abc", tokener.nextString('\'')); tokener.next(); assertEquals('\'', tokener.next()); assertEquals("def", tokener.nextString('\'')); tokener.next(); assertEquals('"', tokener.next()); assertEquals("ghi", tokener.nextString('\"')); assertFalse(tokener.more()); } public void testNextStringNoDelimiter() throws JSONException { try { new JSONTokener("").nextString('\''); fail(); } catch (JSONException e) { } JSONTokener tokener = new JSONTokener(" 'abc"); tokener.next(); tokener.next(); try { tokener.next('\''); fail(); } catch (JSONException e) { } } public void testNextStringEscapedQuote() throws JSONException { try { new JSONTokener("abc\\").nextString('"'); fail(); } catch (JSONException e) { } // we're mixing Java escaping like \" and JavaScript escaping like \\\" // which makes these tests extra tricky to read! assertEquals("abc\"def", new JSONTokener("abc\\\"def\"ghi").nextString('"')); assertEquals("abc\\def", new JSONTokener("abc\\\\def\"ghi").nextString('"')); assertEquals("abc/def", new JSONTokener("abc\\/def\"ghi").nextString('"')); assertEquals("abc\bdef", new JSONTokener("abc\\bdef\"ghi").nextString('"')); assertEquals("abc\fdef", new JSONTokener("abc\\fdef\"ghi").nextString('"')); assertEquals("abc\ndef", new JSONTokener("abc\\ndef\"ghi").nextString('"')); assertEquals("abc\rdef", new JSONTokener("abc\\rdef\"ghi").nextString('"')); assertEquals("abc\tdef", new JSONTokener("abc\\tdef\"ghi").nextString('"')); } public void testNextStringUnicodeEscaped() throws JSONException { // we're mixing Java escaping like \\ and JavaScript escaping like \\u assertEquals("abc def", new JSONTokener("abc\\u0020def\"ghi").nextString('"')); assertEquals("abcU0020def", new JSONTokener("abc\\U0020def\"ghi").nextString('"')); // JSON requires 4 hex characters after a unicode escape try { new JSONTokener("abc\\u002\"").nextString('"'); fail(); } catch (NumberFormatException e) { } catch (JSONException e) { } try { new JSONTokener("abc\\u").nextString('"'); fail(); } catch (JSONException e) { } try { new JSONTokener("abc\\u \"").nextString('"'); fail(); } catch (NumberFormatException e) { } assertEquals("abc\"def", new JSONTokener("abc\\u0022def\"ghi").nextString('"')); try { new JSONTokener("abc\\u000G\"").nextString('"'); fail(); } catch (NumberFormatException e) { } } public void testNextStringNonQuote() throws JSONException { assertEquals("AB", new JSONTokener("ABC").nextString('C')); assertEquals("ABCD", new JSONTokener("AB\\CDC").nextString('C')); assertEquals("AB\nC", new JSONTokener("AB\\nCn").nextString('n')); } public void testNextTo() throws JSONException { assertEquals("ABC", new JSONTokener("ABCDEFG").nextTo("DHI")); assertEquals("ABCDEF", new JSONTokener("ABCDEF").nextTo("")); JSONTokener tokener = new JSONTokener("ABC\rDEF\nGHI\r\nJKL"); assertEquals("ABC", tokener.nextTo("M")); assertEquals('\r', tokener.next()); assertEquals("DEF", tokener.nextTo("M")); assertEquals('\n', tokener.next()); assertEquals("GHI", tokener.nextTo("M")); assertEquals('\r', tokener.next()); assertEquals('\n', tokener.next()); assertEquals("JKL", tokener.nextTo("M")); tokener = new JSONTokener("ABCDEFGHI"); assertEquals("ABC", tokener.nextTo("DEF")); assertEquals("", tokener.nextTo("DEF")); assertEquals('D', tokener.next()); assertEquals("", tokener.nextTo("DEF")); assertEquals('E', tokener.next()); assertEquals("", tokener.nextTo("DEF")); assertEquals('F', tokener.next()); assertEquals("GHI", tokener.nextTo("DEF")); assertEquals("", tokener.nextTo("DEF")); tokener = new JSONTokener(" \t \fABC \t DEF"); assertEquals("ABC", tokener.nextTo("DEF")); assertEquals('D', tokener.next()); tokener = new JSONTokener(" \t \fABC \n DEF"); assertEquals("ABC", tokener.nextTo("\n")); assertEquals("", tokener.nextTo("\n")); tokener = new JSONTokener(""); try { tokener.nextTo(null); fail(); } catch (NullPointerException e) { } } public void testNextToTrimming() { assertEquals("ABC", new JSONTokener("\t ABC \tDEF").nextTo("DE")); assertEquals("ABC", new JSONTokener("\t ABC \tDEF").nextTo('D')); } public void testNextToTrailing() { assertEquals("ABC DEF", new JSONTokener("\t ABC DEF \t").nextTo("G")); assertEquals("ABC DEF", new JSONTokener("\t ABC DEF \t").nextTo('G')); } public void testNextToDoesntStopOnNull() { String message = "nextTo() shouldn't stop after \\0 characters"; JSONTokener tokener = new JSONTokener(" \0\t \fABC \n DEF"); assertEquals(message, "ABC", tokener.nextTo("D")); assertEquals(message, '\n', tokener.next()); assertEquals(message, "", tokener.nextTo("D")); } public void testNextToConsumesNull() { String message = "nextTo shouldn't consume \\0."; JSONTokener tokener = new JSONTokener("ABC\0DEF"); assertEquals(message, "ABC", tokener.nextTo("\0")); assertEquals(message, '\0', tokener.next()); assertEquals(message, "DEF", tokener.nextTo("\0")); } public void testSkipPast() { JSONTokener tokener = new JSONTokener("ABCDEF"); tokener.skipPast("ABC"); assertEquals('D', tokener.next()); tokener.skipPast("EF"); assertEquals('\0', tokener.next()); tokener = new JSONTokener("ABCDEF"); tokener.skipPast("ABCDEF"); assertEquals('\0', tokener.next()); tokener = new JSONTokener("ABCDEF"); tokener.skipPast("G"); assertEquals('\0', tokener.next()); tokener = new JSONTokener("ABC\0ABC"); tokener.skipPast("ABC"); assertEquals('\0', tokener.next()); assertEquals('A', tokener.next()); tokener = new JSONTokener("\0ABC"); tokener.skipPast("ABC"); assertEquals('\0', tokener.next()); tokener = new JSONTokener("ABC\nDEF"); tokener.skipPast("DEF"); assertEquals('\0', tokener.next()); tokener = new JSONTokener("ABC"); tokener.skipPast("ABCDEF"); assertEquals('\0', tokener.next()); tokener = new JSONTokener("ABCDABCDABCD"); tokener.skipPast("ABC"); assertEquals('D', tokener.next()); tokener.skipPast("ABC"); assertEquals('D', tokener.next()); tokener.skipPast("ABC"); assertEquals('D', tokener.next()); tokener = new JSONTokener(""); try { tokener.skipPast(null); fail(); } catch (NullPointerException e) { } } public void testSkipTo() { JSONTokener tokener = new JSONTokener("ABCDEF"); tokener.skipTo('A'); assertEquals('A', tokener.next()); tokener.skipTo('D'); assertEquals('D', tokener.next()); tokener.skipTo('G'); assertEquals('E', tokener.next()); tokener.skipTo('A'); assertEquals('F', tokener.next()); tokener = new JSONTokener("ABC\nDEF"); tokener.skipTo('F'); assertEquals('F', tokener.next()); tokener = new JSONTokener("ABCfDEF"); tokener.skipTo('F'); assertEquals('F', tokener.next()); tokener = new JSONTokener("ABC/* DEF */"); tokener.skipTo('D'); assertEquals('D', tokener.next()); } public void testSkipToStopsOnNull() { JSONTokener tokener = new JSONTokener("ABC\0DEF"); tokener.skipTo('F'); assertEquals("skipTo shouldn't stop when it sees '\\0'", 'F', tokener.next()); } public void testBomIgnoredAsFirstCharacterOfDocument() throws JSONException { JSONTokener tokener = new JSONTokener("\ufeff[]"); JSONArray array = (JSONArray) tokener.nextValue(); assertEquals(0, array.length()); } public void testBomTreatedAsCharacterInRestOfDocument() throws JSONException { JSONTokener tokener = new JSONTokener("[\ufeff]"); JSONArray array = (JSONArray) tokener.nextValue(); assertEquals(1, array.length()); } public void testDehexchar() { assertEquals( 0, JSONTokener.dehexchar('0')); assertEquals( 1, JSONTokener.dehexchar('1')); assertEquals( 2, JSONTokener.dehexchar('2')); assertEquals( 3, JSONTokener.dehexchar('3')); assertEquals( 4, JSONTokener.dehexchar('4')); assertEquals( 5, JSONTokener.dehexchar('5')); assertEquals( 6, JSONTokener.dehexchar('6')); assertEquals( 7, JSONTokener.dehexchar('7')); assertEquals( 8, JSONTokener.dehexchar('8')); assertEquals( 9, JSONTokener.dehexchar('9')); assertEquals(10, JSONTokener.dehexchar('A')); assertEquals(11, JSONTokener.dehexchar('B')); assertEquals(12, JSONTokener.dehexchar('C')); assertEquals(13, JSONTokener.dehexchar('D')); assertEquals(14, JSONTokener.dehexchar('E')); assertEquals(15, JSONTokener.dehexchar('F')); assertEquals(10, JSONTokener.dehexchar('a')); assertEquals(11, JSONTokener.dehexchar('b')); assertEquals(12, JSONTokener.dehexchar('c')); assertEquals(13, JSONTokener.dehexchar('d')); assertEquals(14, JSONTokener.dehexchar('e')); assertEquals(15, JSONTokener.dehexchar('f')); for (int c = 0; c <= 0xFFFF; c++) { if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')) { continue; } assertEquals("dehexchar " + c, -1, JSONTokener.dehexchar((char) c)); } } }