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.lang3.time;
19  
20  import static org.junit.jupiter.api.Assertions.assertEquals;
21  import static org.junit.jupiter.api.Assertions.assertNotNull;
22  import static org.junit.jupiter.api.Assertions.assertSame;
23  import static org.junit.jupiter.api.Assertions.assertThrows;
24  
25  import java.util.TimeZone;
26  
27  import org.apache.commons.lang3.SerializationUtils;
28  import org.junit.jupiter.api.Test;
29  
30  /**
31   * Tests {@link ImmutableTimeZone} and {@link TimeZones#GMT}.
32   * <p>
33   * TimeZones.GMT is a public static final mutable TimeZone. Callers can call setRawOffset() / setID() to globally corrupt time zone behavior.
34   * </p>
35   * <p>
36   * Pre-patch: setRawOffset() / setID() succeed silently and mutate the shared singleton.
37   * </p>
38   * <p>
39   * Post-patch: setRawOffset() / setID() throw UnsupportedOperationException.
40   * </p>
41   */
42  public class TimeZonesGmtTest {
43  
44      @Test
45      public void testCloneReturnsSelfForImmutableSingleton() {
46          // For an immutable singleton, clone() should return the same instance,
47          // and there must be no need to defensively copy the value.
48          final Object copy = TimeZones.GMT.clone();
49          assertSame(TimeZones.GMT, copy, "clone() of an immutable singleton should return the same instance");
50      }
51  
52      @Test
53      public void testGmtIdRemainsGmtAfterAttemptedMutation() {
54          assertThrows(UnsupportedOperationException.class, () -> TimeZones.GMT.setID("Etc/UTC"));
55          assertEquals("GMT", TimeZones.GMT.getID());
56      }
57  
58      @Test
59      public void testGmtOffsetRemainsZeroAfterAttemptedMutation() {
60          assertThrows(UnsupportedOperationException.class, () -> TimeZones.GMT.setRawOffset(3600000));
61          assertEquals(0, TimeZones.GMT.getRawOffset());
62      }
63  
64      @Test
65      public void testGmtTimeZoneIsImmutableSetId() {
66          assertThrows(UnsupportedOperationException.class, () -> TimeZones.GMT.setID("Etc/UTC"));
67      }
68  
69      @Test
70      public void testGmtTimeZoneIsImmutableSetRawOffset() {
71          assertThrows(UnsupportedOperationException.class, () -> TimeZones.GMT.setRawOffset(3600000));
72      }
73  
74      @Test
75      public void testSerializationRoundTripPreservesId() throws Exception {
76          // Round-trip via Serializable to confirm the type is wire-format friendly.
77          final TimeZone roundtrip = SerializationUtils.roundtrip(TimeZones.GMT);
78          assertNotNull(roundtrip, "Round-tripped TimeZone must not be null");
79          assertEquals("GMT", roundtrip.getID(), "Round-tripped TimeZone id must remain \"GMT\"");
80          assertEquals(0, roundtrip.getRawOffset(), "Round-tripped TimeZone offset must remain 0");
81      }
82  
83      @Test
84      public void testSerializedFormUsesNamedClass() throws Exception {
85          // Wire-format hazard guard: ensure the implementation class is a named static
86          // nested type, not an anonymous inner class whose synthetic name (TimeZones$1)
87          // is brittle across non-functional code reorderings.
88          assertEquals(ImmutableTimeZone.class.getName(), TimeZones.GMT.getClass().getName());
89      }
90  }