Back to Repositories

Validating JPMS Module Exports and Package Accessibility in google/gson

This test suite validates the proper configuration and accessibility of Gson’s module exports through JPMS (Java Platform Module System). It ensures that public API packages are correctly exported while maintaining appropriate encapsulation of internal components.

Test Coverage Overview

The test suite provides comprehensive coverage of Gson’s modular architecture, examining package exports and reflection access controls. Key areas tested include:

  • Main Gson package functionality verification
  • Annotations package accessibility and usage
  • Reflect package type token operations
  • Stream package JSON parsing capabilities
  • Internal package access restrictions

Implementation Analysis

The testing approach employs JUnit framework with targeted test cases for each major package component. The implementation uses a combination of direct API calls and reflection-based testing to verify both positive and negative access scenarios. Technical patterns include exception verification for unauthorized access and validation of module boundary enforcement.

Technical Details

Testing infrastructure includes:

  • JUnit test framework
  • Google Truth assertion library
  • JPMS module system integration
  • Reflection API for boundary testing
  • Custom test classes with annotations

Best Practices Demonstrated

The test suite exemplifies several testing best practices:

  • Isolation of test cases by package functionality
  • Comprehensive boundary testing
  • Clear test method naming conventions
  • Explicit verification of security boundaries
  • Structured validation of module exports

google/gson

test-jpms/src/test/java/com/google/gson/jpms_test/ExportedPackagesTest.java

            
/*
 * Copyright (C) 2024 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.jpms_test;

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

import com.google.gson.Gson;
import com.google.gson.annotations.SerializedName;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import java.io.IOException;
import java.io.StringReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InaccessibleObjectException;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import org.junit.Test;

/**
 * Verifies that Gson's {@code module-info.class} properly 'exports' all its packages containing
 * public API.
 */
public class ExportedPackagesTest {
  /** Tests package {@code com.google.gson} */
  @Test
  public void testMainPackage() {
    Gson gson = new Gson();
    assertThat(gson.toJson(1)).isEqualTo("1");
  }

  /** Tests package {@code com.google.gson.annotations} */
  @Test
  public void testAnnotationsPackage() throws Exception {
    class Annotated {
      @SerializedName("custom-name")
      int i;
    }

    Field field = Annotated.class.getDeclaredField("i");
    SerializedName annotation = field.getAnnotation(SerializedName.class);
    assertThat(annotation.value()).isEqualTo("custom-name");
  }

  /** Tests package {@code com.google.gson.reflect} */
  @Test
  public void testReflectPackage() {
    var typeToken = TypeToken.get(String.class);
    assertThat(typeToken.getRawType()).isEqualTo(String.class);
  }

  /** Tests package {@code com.google.gson.stream} */
  @Test
  public void testStreamPackage() throws IOException {
    JsonReader jsonReader = new JsonReader(new StringReader("2"));
    assertThat(jsonReader.nextInt()).isEqualTo(2);
  }

  /** Verifies that Gson packages are only 'exported' but not 'opened' for reflection. */
  @Test
  public void testReflectionInternalField() throws Exception {
    Gson gson = new Gson();

    // Get an arbitrary non-public instance field
    Field field =
        Arrays.stream(Gson.class.getDeclaredFields())
            .filter(
                f -> !Modifier.isStatic(f.getModifiers()) && !Modifier.isPublic(f.getModifiers()))
            .findFirst()
            .get();
    assertThrows(InaccessibleObjectException.class, () -> field.setAccessible(true));
    assertThrows(IllegalAccessException.class, () -> field.get(gson));
  }

  @Test
  public void testInaccessiblePackage() throws Exception {
    // Note: In case this class is renamed / removed, can change this to any other internal class
    Class<?> internalClass = Class.forName("com.google.gson.internal.LinkedTreeMap");
    assertThat(Modifier.isPublic(internalClass.getModifiers())).isTrue();
    // Get the public constructor
    Constructor<?> constructor = internalClass.getConstructor();
    assertThrows(InaccessibleObjectException.class, () -> constructor.setAccessible(true));
    assertThrows(IllegalAccessException.class, () -> constructor.newInstance());
  }
}