1 /*
2  * Copyright 2018, OpenCensus Authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package io.opencensus.trace;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import com.google.common.testing.EqualsTester;
22 import io.opencensus.trace.Tracestate.Entry;
23 import java.util.Arrays;
24 import org.junit.Rule;
25 import org.junit.Test;
26 import org.junit.rules.ExpectedException;
27 import org.junit.runner.RunWith;
28 import org.junit.runners.JUnit4;
29 
30 /** Unit tests for {@link Tracestate}. */
31 @RunWith(JUnit4.class)
32 public class TracestateTest {
33   private static final String FIRST_KEY = "key_1";
34   private static final String SECOND_KEY = "key_2";
35   private static final String FIRST_VALUE = "value.1";
36   private static final String SECOND_VALUE = "value.2";
37 
38   @Rule public final ExpectedException thrown = ExpectedException.none();
39 
40   private static final Tracestate EMPTY = Tracestate.builder().build();
41   private final Tracestate firstTracestate = EMPTY.toBuilder().set(FIRST_KEY, FIRST_VALUE).build();
42   private final Tracestate secondTracestate =
43       EMPTY.toBuilder().set(SECOND_KEY, SECOND_VALUE).build();
44   private final Tracestate multiValueTracestate =
45       EMPTY.toBuilder().set(FIRST_KEY, FIRST_VALUE).set(SECOND_KEY, SECOND_VALUE).build();
46 
47   @Test
get()48   public void get() {
49     assertThat(firstTracestate.get(FIRST_KEY)).isEqualTo(FIRST_VALUE);
50     assertThat(secondTracestate.get(SECOND_KEY)).isEqualTo(SECOND_VALUE);
51     assertThat(multiValueTracestate.get(FIRST_KEY)).isEqualTo(FIRST_VALUE);
52     assertThat(multiValueTracestate.get(SECOND_KEY)).isEqualTo(SECOND_VALUE);
53   }
54 
55   @Test
getEntries()56   public void getEntries() {
57     assertThat(firstTracestate.getEntries()).containsExactly(Entry.create(FIRST_KEY, FIRST_VALUE));
58     assertThat(secondTracestate.getEntries())
59         .containsExactly(Entry.create(SECOND_KEY, SECOND_VALUE));
60     assertThat(multiValueTracestate.getEntries())
61         .containsExactly(
62             Entry.create(FIRST_KEY, FIRST_VALUE), Entry.create(SECOND_KEY, SECOND_VALUE));
63   }
64 
65   @Test
disallowsNullKey()66   public void disallowsNullKey() {
67     thrown.expect(NullPointerException.class);
68     EMPTY.toBuilder().set(null, FIRST_VALUE).build();
69   }
70 
71   @Test
invalidFirstKeyCharacter()72   public void invalidFirstKeyCharacter() {
73     thrown.expect(IllegalArgumentException.class);
74     EMPTY.toBuilder().set("1_key", FIRST_VALUE).build();
75   }
76 
77   @Test
invalidKeyCharacters()78   public void invalidKeyCharacters() {
79     thrown.expect(IllegalArgumentException.class);
80     EMPTY.toBuilder().set("kEy_1", FIRST_VALUE).build();
81   }
82 
83   @Test
invalidKeySize()84   public void invalidKeySize() {
85     char[] chars = new char[257];
86     Arrays.fill(chars, 'a');
87     String longKey = new String(chars);
88     thrown.expect(IllegalArgumentException.class);
89     EMPTY.toBuilder().set(longKey, FIRST_VALUE).build();
90   }
91 
92   @Test
allAllowedKeyCharacters()93   public void allAllowedKeyCharacters() {
94     StringBuilder stringBuilder = new StringBuilder();
95     for (char c = 'a'; c <= 'z'; c++) {
96       stringBuilder.append(c);
97     }
98     for (char c = '0'; c <= '9'; c++) {
99       stringBuilder.append(c);
100     }
101     stringBuilder.append('_');
102     stringBuilder.append('-');
103     stringBuilder.append('*');
104     stringBuilder.append('/');
105     String allowedKey = stringBuilder.toString();
106     assertThat(EMPTY.toBuilder().set(allowedKey, FIRST_VALUE).build().get(allowedKey))
107         .isEqualTo(FIRST_VALUE);
108   }
109 
110   @Test
disallowsNullValue()111   public void disallowsNullValue() {
112     thrown.expect(NullPointerException.class);
113     EMPTY.toBuilder().set(FIRST_KEY, null).build();
114   }
115 
116   @Test
valueCannotContainEqual()117   public void valueCannotContainEqual() {
118     thrown.expect(IllegalArgumentException.class);
119     EMPTY.toBuilder().set(FIRST_KEY, "my_vakue=5").build();
120   }
121 
122   @Test
valueCannotContainComma()123   public void valueCannotContainComma() {
124     thrown.expect(IllegalArgumentException.class);
125     EMPTY.toBuilder().set(FIRST_KEY, "first,second").build();
126   }
127 
128   @Test
valueCannotContainTrailingSpaces()129   public void valueCannotContainTrailingSpaces() {
130     thrown.expect(IllegalArgumentException.class);
131     EMPTY.toBuilder().set(FIRST_KEY, "first ").build();
132   }
133 
134   @Test
invalidValueSize()135   public void invalidValueSize() {
136     char[] chars = new char[257];
137     Arrays.fill(chars, 'a');
138     String longValue = new String(chars);
139     thrown.expect(IllegalArgumentException.class);
140     EMPTY.toBuilder().set(FIRST_KEY, longValue).build();
141   }
142 
143   @Test
allAllowedValueCharacters()144   public void allAllowedValueCharacters() {
145     StringBuilder stringBuilder = new StringBuilder();
146     for (char c = ' ' /* '\u0020' */; c <= '~' /* '\u007E' */; c++) {
147       if (c == ',' || c == '=') {
148         continue;
149       }
150       stringBuilder.append(c);
151     }
152     String allowedValue = stringBuilder.toString();
153     assertThat(EMPTY.toBuilder().set(FIRST_KEY, allowedValue).build().get(FIRST_KEY))
154         .isEqualTo(allowedValue);
155   }
156 
157   @Test
addEntry()158   public void addEntry() {
159     assertThat(firstTracestate.toBuilder().set(SECOND_KEY, SECOND_VALUE).build())
160         .isEqualTo(multiValueTracestate);
161   }
162 
163   @Test
updateEntry()164   public void updateEntry() {
165     assertThat(firstTracestate.toBuilder().set(FIRST_KEY, SECOND_VALUE).build().get(FIRST_KEY))
166         .isEqualTo(SECOND_VALUE);
167     Tracestate updatedMultiValueTracestate =
168         multiValueTracestate.toBuilder().set(FIRST_KEY, SECOND_VALUE).build();
169     assertThat(updatedMultiValueTracestate.get(FIRST_KEY)).isEqualTo(SECOND_VALUE);
170     assertThat(updatedMultiValueTracestate.get(SECOND_KEY)).isEqualTo(SECOND_VALUE);
171   }
172 
173   @Test
addAndUpdateEntry()174   public void addAndUpdateEntry() {
175     assertThat(
176             firstTracestate
177                 .toBuilder()
178                 .set(FIRST_KEY, SECOND_VALUE) // update the existing entry
179                 .set(SECOND_KEY, FIRST_VALUE) // add a new entry
180                 .build()
181                 .getEntries())
182         .containsExactly(
183             Entry.create(FIRST_KEY, SECOND_VALUE), Entry.create(SECOND_KEY, FIRST_VALUE));
184   }
185 
186   @Test
addSameKey()187   public void addSameKey() {
188     assertThat(
189             EMPTY
190                 .toBuilder()
191                 .set(FIRST_KEY, SECOND_VALUE) // update the existing entry
192                 .set(FIRST_KEY, FIRST_VALUE) // add a new entry
193                 .build()
194                 .getEntries())
195         .containsExactly(Entry.create(FIRST_KEY, FIRST_VALUE));
196   }
197 
198   @Test
remove()199   public void remove() {
200     assertThat(multiValueTracestate.toBuilder().remove(SECOND_KEY).build())
201         .isEqualTo(firstTracestate);
202   }
203 
204   @Test
addAndRemoveEntry()205   public void addAndRemoveEntry() {
206     assertThat(
207             EMPTY
208                 .toBuilder()
209                 .set(FIRST_KEY, SECOND_VALUE) // update the existing entry
210                 .remove(FIRST_KEY) // add a new entry
211                 .build())
212         .isEqualTo(EMPTY);
213   }
214 
215   @Test
remove_NullNotAllowed()216   public void remove_NullNotAllowed() {
217     thrown.expect(NullPointerException.class);
218     multiValueTracestate.toBuilder().remove(null).build();
219   }
220 
221   @Test
tracestate_EqualsAndHashCode()222   public void tracestate_EqualsAndHashCode() {
223     EqualsTester tester = new EqualsTester();
224     tester.addEqualityGroup(EMPTY, EMPTY);
225     tester.addEqualityGroup(firstTracestate, EMPTY.toBuilder().set(FIRST_KEY, FIRST_VALUE).build());
226     tester.addEqualityGroup(
227         secondTracestate, EMPTY.toBuilder().set(SECOND_KEY, SECOND_VALUE).build());
228     tester.testEquals();
229   }
230 
231   @Test
tracestate_ToString()232   public void tracestate_ToString() {
233     assertThat(EMPTY.toString()).isEqualTo("Tracestate{entries=[]}");
234   }
235 }
236