Back to Repositories

Validating Number Parsing Strategies in google/gson

This test suite validates the ToNumberPolicy class in Google’s Gson library, focusing on different number parsing strategies and their handling of various numeric formats. It ensures robust JSON number handling across different data types and edge cases.

Test Coverage Overview

The test suite provides comprehensive coverage of number parsing strategies including DOUBLE, LAZILY_PARSED_NUMBER, LONG_OR_DOUBLE, and BIG_DECIMAL implementations. Key functionality tested includes:

  • Decimal number parsing accuracy
  • Scientific notation handling
  • Infinity and NaN processing
  • Error handling for malformed inputs
  • Null value validation

Implementation Analysis

The testing approach uses JUnit framework with Truth assertions for precise numeric comparisons. Each number policy is tested through dedicated methods that verify parsing behavior and error conditions. The implementation leverages JsonReader for input processing and includes specific checks for strictness modes.

Notable patterns include:
  • Systematic error case validation
  • Precision testing for floating-point numbers
  • Boundary condition verification

Technical Details

Testing infrastructure includes:

  • JUnit 4 test framework
  • Google Truth assertion library
  • Custom JsonReader implementations
  • StringReader for input simulation
  • Various number type implementations (BigDecimal, Double, LazilyParsedNumber)

Best Practices Demonstrated

The test suite exemplifies high-quality testing practices through:

  • Comprehensive edge case coverage
  • Clear test method organization
  • Explicit error message validation
  • Proper exception type checking
  • Consistent test method naming
  • Isolated test cases for each number policy

google/gson

gson/src/test/java/com/google/gson/ToNumberPolicyTest.java

            
/*
 * Copyright (C) 2021 Google Inc.
 *
 * 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 com.google.gson;

import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;

import com.google.gson.internal.LazilyParsedNumber;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.MalformedJsonException;
import java.io.IOException;
import java.io.StringReader;
import java.math.BigDecimal;
import org.junit.Test;

public class ToNumberPolicyTest {
  @Test
  public void testDouble() throws IOException {
    ToNumberStrategy strategy = ToNumberPolicy.DOUBLE;
    assertThat(strategy.readNumber(fromString("10.1"))).isEqualTo(10.1);
    assertThat(strategy.readNumber(fromString("3.141592653589793238462643383279")))
        .isEqualTo(3.141592653589793D);

    MalformedJsonException e =
        assertThrows(MalformedJsonException.class, () -> strategy.readNumber(fromString("1e400")));
    assertThat(e)
        .hasMessageThat()
        .isEqualTo(
            "JSON forbids NaN and infinities: Infinity at line 1 column 6 path $\n"
                + "See https://github.com/google/gson/blob/main/Troubleshooting.md#malformed-json");

    assertThrows(
        NumberFormatException.class, () -> strategy.readNumber(fromString("\"not-a-number\"")));
  }

  @Test
  public void testLazilyParsedNumber() throws IOException {
    ToNumberStrategy strategy = ToNumberPolicy.LAZILY_PARSED_NUMBER;
    assertThat(strategy.readNumber(fromString("10.1"))).isEqualTo(new LazilyParsedNumber("10.1"));
    assertThat(strategy.readNumber(fromString("3.141592653589793238462643383279")))
        .isEqualTo(new LazilyParsedNumber("3.141592653589793238462643383279"));
    assertThat(strategy.readNumber(fromString("1e400"))).isEqualTo(new LazilyParsedNumber("1e400"));
  }

  @Test
  public void testLongOrDouble() throws IOException {
    ToNumberStrategy strategy = ToNumberPolicy.LONG_OR_DOUBLE;
    assertThat(strategy.readNumber(fromString("10"))).isEqualTo(10L);
    assertThat(strategy.readNumber(fromString("10.1"))).isEqualTo(10.1);
    assertThat(strategy.readNumber(fromString("3.141592653589793238462643383279")))
        .isEqualTo(3.141592653589793D);

    Exception e =
        assertThrows(MalformedJsonException.class, () -> strategy.readNumber(fromString("1e400")));
    assertThat(e)
        .hasMessageThat()
        .isEqualTo("JSON forbids NaN and infinities: Infinity; at path $");

    e =
        assertThrows(
            JsonParseException.class, () -> strategy.readNumber(fromString("\"not-a-number\"")));
    assertThat(e).hasMessageThat().isEqualTo("Cannot parse not-a-number; at path $");

    assertThat(strategy.readNumber(fromStringLenient("NaN"))).isEqualTo(Double.NaN);
    assertThat(strategy.readNumber(fromStringLenient("Infinity")))
        .isEqualTo(Double.POSITIVE_INFINITY);
    assertThat(strategy.readNumber(fromStringLenient("-Infinity")))
        .isEqualTo(Double.NEGATIVE_INFINITY);

    e = assertThrows(MalformedJsonException.class, () -> strategy.readNumber(fromString("NaN")));
    assertThat(e)
        .hasMessageThat()
        .isEqualTo(
            "Use JsonReader.setStrictness(Strictness.LENIENT) to accept malformed JSON at line 1"
                + " column 1 path $\n"
                + "See https://github.com/google/gson/blob/main/Troubleshooting.md#malformed-json");

    e =
        assertThrows(
            MalformedJsonException.class, () -> strategy.readNumber(fromString("Infinity")));
    assertThat(e)
        .hasMessageThat()
        .isEqualTo(
            "Use JsonReader.setStrictness(Strictness.LENIENT) to accept malformed JSON at line 1"
                + " column 1 path $\n"
                + "See https://github.com/google/gson/blob/main/Troubleshooting.md#malformed-json");

    e =
        assertThrows(
            MalformedJsonException.class, () -> strategy.readNumber(fromString("-Infinity")));
    assertThat(e)
        .hasMessageThat()
        .isEqualTo(
            "Use JsonReader.setStrictness(Strictness.LENIENT) to accept malformed JSON at line 1"
                + " column 1 path $\n"
                + "See https://github.com/google/gson/blob/main/Troubleshooting.md#malformed-json");
  }

  @Test
  public void testBigDecimal() throws IOException {
    ToNumberStrategy strategy = ToNumberPolicy.BIG_DECIMAL;
    assertThat(strategy.readNumber(fromString("10.1"))).isEqualTo(new BigDecimal("10.1"));
    assertThat(strategy.readNumber(fromString("3.141592653589793238462643383279")))
        .isEqualTo(new BigDecimal("3.141592653589793238462643383279"));
    assertThat(strategy.readNumber(fromString("1e400"))).isEqualTo(new BigDecimal("1e400"));

    JsonParseException e =
        assertThrows(
            JsonParseException.class, () -> strategy.readNumber(fromString("\"not-a-number\"")));
    assertThat(e).hasMessageThat().isEqualTo("Cannot parse not-a-number; at path $");
  }

  @Test
  public void testNullsAreNeverExpected() throws IOException {
    IllegalStateException e =
        assertThrows(
            IllegalStateException.class,
            () -> ToNumberPolicy.DOUBLE.readNumber(fromString("null")));
    assertThat(e)
        .hasMessageThat()
        .isEqualTo(
            "Expected a double but was NULL at line 1 column 5 path $\n"
                + "See https://github.com/google/gson/blob/main/Troubleshooting.md#adapter-not-null-safe");

    e =
        assertThrows(
            IllegalStateException.class,
            () -> ToNumberPolicy.LAZILY_PARSED_NUMBER.readNumber(fromString("null")));
    assertThat(e)
        .hasMessageThat()
        .isEqualTo(
            "Expected a string but was NULL at line 1 column 5 path $\n"
                + "See https://github.com/google/gson/blob/main/Troubleshooting.md#adapter-not-null-safe");

    e =
        assertThrows(
            IllegalStateException.class,
            () -> ToNumberPolicy.LONG_OR_DOUBLE.readNumber(fromString("null")));
    assertThat(e)
        .hasMessageThat()
        .isEqualTo(
            "Expected a string but was NULL at line 1 column 5 path $\n"
                + "See https://github.com/google/gson/blob/main/Troubleshooting.md#adapter-not-null-safe");

    e =
        assertThrows(
            IllegalStateException.class,
            () -> ToNumberPolicy.BIG_DECIMAL.readNumber(fromString("null")));
    assertThat(e)
        .hasMessageThat()
        .isEqualTo(
            "Expected a string but was NULL at line 1 column 5 path $\n"
                + "See https://github.com/google/gson/blob/main/Troubleshooting.md#adapter-not-null-safe");
  }

  private static JsonReader fromString(String json) {
    return new JsonReader(new StringReader(json));
  }

  private static JsonReader fromStringLenient(String json) {
    JsonReader jsonReader = fromString(json);
    jsonReader.setStrictness(Strictness.LENIENT);
    return jsonReader;
  }
}