Skip to content

Commit 38b6f39

Browse files
committed
Number Semantics Test
1 parent 5324096 commit 38b6f39

File tree

1 file changed

+297
-0
lines changed

1 file changed

+297
-0
lines changed
Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.firebase.firestore.pipeline
16+
17+
import com.google.common.truth.Truth.assertThat
18+
import com.google.firebase.firestore.RealtimePipelineSource
19+
import com.google.firebase.firestore.TestUtil
20+
import com.google.firebase.firestore.pipeline.Expr.Companion.array
21+
import com.google.firebase.firestore.pipeline.Expr.Companion.arrayContains
22+
import com.google.firebase.firestore.pipeline.Expr.Companion.arrayContainsAny
23+
import com.google.firebase.firestore.pipeline.Expr.Companion.constant
24+
import com.google.firebase.firestore.pipeline.Expr.Companion.field
25+
import com.google.firebase.firestore.pipeline.Expr.Companion.notEqAny
26+
import com.google.firebase.firestore.runPipeline
27+
import com.google.firebase.firestore.testutil.TestUtilKtx.doc
28+
import kotlinx.coroutines.flow.flowOf
29+
import kotlinx.coroutines.flow.toList
30+
import kotlinx.coroutines.runBlocking
31+
import org.junit.Test
32+
import org.junit.runner.RunWith
33+
import org.robolectric.RobolectricTestRunner
34+
35+
@RunWith(RobolectricTestRunner::class)
36+
internal class NumberSemanticsTests {
37+
38+
private val db = TestUtil.firestore()
39+
40+
@Test
41+
fun `zero negative double zero`(): Unit = runBlocking {
42+
val doc1 = doc("users/a", 1000, mapOf("score" to 0L)) // Integer 0
43+
val doc3 = doc("users/c", 1000, mapOf("score" to 0.0)) // Double 0.0
44+
val doc4 = doc("users/d", 1000, mapOf("score" to -0.0)) // Double -0.0
45+
val doc5 = doc("users/e", 1000, mapOf("score" to 1L)) // Integer 1
46+
val documents = listOf(doc1, doc3, doc4, doc5)
47+
48+
val pipeline =
49+
RealtimePipelineSource(db).collection("users").where(field("score").eq(constant(-0.0)))
50+
51+
val result = runPipeline(db, pipeline, flowOf(*documents.toTypedArray())).toList()
52+
assertThat(result).containsExactlyElementsIn(listOf(doc1, doc3, doc4))
53+
}
54+
55+
@Test
56+
fun `zero negative integer zero`(): Unit = runBlocking {
57+
val doc1 = doc("users/a", 1000, mapOf("score" to 0L))
58+
val doc3 = doc("users/c", 1000, mapOf("score" to 0.0))
59+
val doc4 = doc("users/d", 1000, mapOf("score" to -0.0))
60+
val doc5 = doc("users/e", 1000, mapOf("score" to 1L))
61+
val documents = listOf(doc1, doc3, doc4, doc5)
62+
63+
val pipeline =
64+
RealtimePipelineSource(db)
65+
.collection("users")
66+
.where(field("score").eq(constant(0L))) // Firestore -0LL is 0L
67+
68+
val result = runPipeline(db, pipeline, flowOf(*documents.toTypedArray())).toList()
69+
assertThat(result).containsExactlyElementsIn(listOf(doc1, doc3, doc4))
70+
}
71+
72+
@Test
73+
fun `zero positive double zero`(): Unit = runBlocking {
74+
val doc1 = doc("users/a", 1000, mapOf("score" to 0L))
75+
val doc3 = doc("users/c", 1000, mapOf("score" to 0.0))
76+
val doc4 = doc("users/d", 1000, mapOf("score" to -0.0))
77+
val doc5 = doc("users/e", 1000, mapOf("score" to 1L))
78+
val documents = listOf(doc1, doc3, doc4, doc5)
79+
80+
val pipeline =
81+
RealtimePipelineSource(db).collection("users").where(field("score").eq(constant(0.0)))
82+
83+
val result = runPipeline(db, pipeline, flowOf(*documents.toTypedArray())).toList()
84+
assertThat(result).containsExactlyElementsIn(listOf(doc1, doc3, doc4))
85+
}
86+
87+
@Test
88+
fun `zero positive integer zero`(): Unit = runBlocking {
89+
val doc1 = doc("users/a", 1000, mapOf("score" to 0L))
90+
val doc3 = doc("users/c", 1000, mapOf("score" to 0.0))
91+
val doc4 = doc("users/d", 1000, mapOf("score" to -0.0))
92+
val doc5 = doc("users/e", 1000, mapOf("score" to 1L))
93+
val documents = listOf(doc1, doc3, doc4, doc5)
94+
95+
val pipeline =
96+
RealtimePipelineSource(db).collection("users").where(field("score").eq(constant(0L)))
97+
98+
val result = runPipeline(db, pipeline, flowOf(*documents.toTypedArray())).toList()
99+
assertThat(result).containsExactlyElementsIn(listOf(doc1, doc3, doc4))
100+
}
101+
102+
@Test
103+
fun `equal Nan`(): Unit = runBlocking {
104+
val doc1 = doc("users/a", 1000, mapOf("name" to "alice", "age" to Double.NaN))
105+
val doc2 = doc("users/b", 1000, mapOf("name" to "bob", "age" to 25L))
106+
val doc3 = doc("users/c", 1000, mapOf("name" to "charlie", "age" to 100L))
107+
val documents = listOf(doc1, doc2, doc3)
108+
109+
val pipeline =
110+
RealtimePipelineSource(db).collection("users").where(field("age").eq(constant(Double.NaN)))
111+
112+
val result = runPipeline(db, pipeline, flowOf(*documents.toTypedArray())).toList()
113+
assertThat(result).isEmpty()
114+
}
115+
116+
@Test
117+
fun `less than Nan`(): Unit = runBlocking {
118+
val doc1 = doc("users/a", 1000, mapOf("name" to "alice", "age" to Double.NaN))
119+
val doc2 = doc("users/b", 1000, mapOf("name" to "bob", "age" to null))
120+
val doc3 = doc("users/c", 1000, mapOf("name" to "charlie", "age" to 100L))
121+
val documents = listOf(doc1, doc2, doc3)
122+
123+
val pipeline =
124+
RealtimePipelineSource(db).collection("users").where(field("age").lt(constant(Double.NaN)))
125+
126+
val result = runPipeline(db, pipeline, flowOf(*documents.toTypedArray())).toList()
127+
assertThat(result).isEmpty()
128+
}
129+
130+
@Test
131+
fun `less than equal Nan`(): Unit = runBlocking {
132+
val doc1 = doc("users/a", 1000, mapOf("name" to "alice", "age" to Double.NaN))
133+
val doc2 = doc("users/b", 1000, mapOf("name" to "bob", "age" to null))
134+
val doc3 = doc("users/c", 1000, mapOf("name" to "charlie", "age" to 100L))
135+
val documents = listOf(doc1, doc2, doc3)
136+
137+
val pipeline =
138+
RealtimePipelineSource(db).collection("users").where(field("age").lte(constant(Double.NaN)))
139+
140+
val result = runPipeline(db, pipeline, flowOf(*documents.toTypedArray())).toList()
141+
assertThat(result).isEmpty()
142+
}
143+
144+
@Test
145+
fun `greater than equal Nan`(): Unit = runBlocking {
146+
val doc1 = doc("users/a", 1000, mapOf("name" to "alice", "age" to Double.NaN))
147+
val doc2 = doc("users/b", 1000, mapOf("name" to "bob", "age" to 100L))
148+
val doc3 = doc("users/c", 1000, mapOf("name" to "charlie", "age" to 100L))
149+
val documents = listOf(doc1, doc2, doc3)
150+
151+
val pipeline =
152+
RealtimePipelineSource(db).collection("users").where(field("age").gte(constant(Double.NaN)))
153+
154+
val result = runPipeline(db, pipeline, flowOf(*documents.toTypedArray())).toList()
155+
assertThat(result).isEmpty()
156+
}
157+
158+
@Test
159+
fun `greater than Nan`(): Unit = runBlocking {
160+
val doc1 = doc("users/a", 1000, mapOf("name" to "alice", "age" to Double.NaN))
161+
val doc2 = doc("users/b", 1000, mapOf("name" to "bob", "age" to 100L))
162+
val doc3 = doc("users/c", 1000, mapOf("name" to "charlie", "age" to 100L))
163+
val documents = listOf(doc1, doc2, doc3)
164+
165+
val pipeline =
166+
RealtimePipelineSource(db).collection("users").where(field("age").gt(constant(Double.NaN)))
167+
168+
val result = runPipeline(db, pipeline, flowOf(*documents.toTypedArray())).toList()
169+
assertThat(result).isEmpty()
170+
}
171+
172+
@Test
173+
fun `not equal Nan`(): Unit = runBlocking {
174+
val doc1 = doc("users/a", 1000, mapOf("name" to "alice", "age" to Double.NaN))
175+
val doc2 = doc("users/b", 1000, mapOf("name" to "bob", "age" to 25L))
176+
val doc3 = doc("users/c", 1000, mapOf("name" to "charlie", "age" to 100L))
177+
val documents = listOf(doc1, doc2, doc3)
178+
179+
val pipeline =
180+
RealtimePipelineSource(db).collection("users").where(field("age").neq(constant(Double.NaN)))
181+
182+
val result = runPipeline(db, pipeline, flowOf(*documents.toTypedArray())).toList()
183+
assertThat(result).containsExactlyElementsIn(listOf(doc1, doc2, doc3))
184+
}
185+
186+
@Test
187+
fun `eqAny contains Nan`(): Unit = runBlocking {
188+
val doc1 = doc("users/a", 1000, mapOf("name" to "alice", "age" to 75.5))
189+
val doc2 = doc("users/b", 1000, mapOf("name" to "bob", "age" to 25L))
190+
val doc3 = doc("users/c", 1000, mapOf("name" to "charlie", "age" to 100L))
191+
val documents = listOf(doc1, doc2, doc3)
192+
193+
val pipeline =
194+
RealtimePipelineSource(db)
195+
.collection("users")
196+
.where(field("name").eqAny(array(Double.NaN, "alice")))
197+
198+
val result = runPipeline(db, pipeline, flowOf(*documents.toTypedArray())).toList()
199+
assertThat(result).containsExactly(doc1)
200+
}
201+
202+
@Test
203+
fun `eqAny contains Nan only is empty`(): Unit = runBlocking {
204+
val doc1 = doc("users/a", 1000, mapOf("name" to "alice", "age" to Double.NaN))
205+
val doc2 = doc("users/b", 1000, mapOf("name" to "bob", "age" to 25L))
206+
val doc3 = doc("users/c", 1000, mapOf("name" to "charlie", "age" to 100L))
207+
val documents = listOf(doc1, doc2, doc3)
208+
209+
val pipeline =
210+
RealtimePipelineSource(db).collection("users").where(field("age").eqAny(array(Double.NaN)))
211+
212+
val result = runPipeline(db, pipeline, flowOf(*documents.toTypedArray())).toList()
213+
assertThat(result).isEmpty()
214+
}
215+
216+
@Test
217+
fun `arrayContains Nan only is empty`(): Unit = runBlocking {
218+
// Documents where 'age' is scalar, not an array.
219+
// arrayContains should not match if the field is not an array or if element is NaN.
220+
val doc1 = doc("users/a", 1000, mapOf("name" to "alice", "age" to Double.NaN))
221+
val doc2 = doc("users/b", 1000, mapOf("name" to "bob", "age" to 25L))
222+
val doc3 = doc("users/c", 1000, mapOf("name" to "charlie", "age" to 100L))
223+
// Example doc if 'age' were an array:
224+
// val docWithArray = doc("users/d", 1000, mapOf("name" to "diana", "age" to
225+
// listOf(Double.NaN)))
226+
val documents = listOf(doc1, doc2, doc3)
227+
228+
val pipeline =
229+
RealtimePipelineSource(db)
230+
.collection("users")
231+
.where(arrayContains(field("age"), constant(Double.NaN)))
232+
233+
val result = runPipeline(db, pipeline, flowOf(*documents.toTypedArray())).toList()
234+
assertThat(result).isEmpty()
235+
}
236+
237+
@Test
238+
fun `arrayContainsAny with Nan`(): Unit = runBlocking {
239+
val doc1 = doc("k/a", 1000, mapOf("field" to listOf(Double.NaN)))
240+
val doc2 = doc("k/b", 1000, mapOf("field" to listOf(Double.NaN, 42L)))
241+
val doc3 = doc("k/c", 1000, mapOf("field" to listOf("foo", 42L)))
242+
val documents = listOf(doc1, doc2, doc3)
243+
244+
val pipeline =
245+
RealtimePipelineSource(db)
246+
.collection("k")
247+
.where(arrayContainsAny(field("field"), array(Double.NaN, "foo")))
248+
249+
val result = runPipeline(db, pipeline, flowOf(*documents.toTypedArray())).toList()
250+
assertThat(result).containsExactly(doc3)
251+
}
252+
253+
@Test
254+
fun `notEqAny contains Nan`(): Unit = runBlocking {
255+
val doc1 = doc("users/a", 1000, mapOf("age" to 42L))
256+
val doc2 = doc("users/b", 1000, mapOf("age" to Double.NaN))
257+
val doc3 = doc("users/c", 1000, mapOf("age" to 25L))
258+
val documents = listOf(doc1, doc2, doc3)
259+
260+
val pipeline =
261+
RealtimePipelineSource(db)
262+
.collection("users")
263+
.where(notEqAny(field("age"), array(Double.NaN, 42L)))
264+
265+
val result = runPipeline(db, pipeline, flowOf(*documents.toTypedArray())).toList()
266+
assertThat(result).containsExactlyElementsIn(listOf(doc2, doc3))
267+
}
268+
269+
@Test
270+
fun `notEqAny contains Nan only matches all`(): Unit = runBlocking {
271+
val doc1 = doc("users/a", 1000, mapOf("age" to 42L))
272+
val doc2 = doc("users/b", 1000, mapOf("age" to Double.NaN))
273+
val doc3 = doc("users/c", 1000, mapOf("age" to 25L))
274+
val documents = listOf(doc1, doc2, doc3)
275+
276+
val pipeline =
277+
RealtimePipelineSource(db)
278+
.collection("users")
279+
.where(notEqAny(field("age"), array(Double.NaN)))
280+
281+
val result = runPipeline(db, pipeline, flowOf(*documents.toTypedArray())).toList()
282+
assertThat(result).containsExactlyElementsIn(listOf(doc1, doc2, doc3))
283+
}
284+
285+
@Test
286+
fun `array with Nan`(): Unit = runBlocking {
287+
val doc1 = doc("k/a", 1000, mapOf("foo" to listOf(Double.NaN)))
288+
val doc2 = doc("k/b", 1000, mapOf("foo" to listOf(42L)))
289+
val documents = listOf(doc1, doc2)
290+
291+
val pipeline =
292+
RealtimePipelineSource(db).collection("k").where(field("foo").eq(array(Double.NaN)))
293+
294+
val result = runPipeline(db, pipeline, flowOf(*documents.toTypedArray())).toList()
295+
assertThat(result).isEmpty()
296+
}
297+
}

0 commit comments

Comments
 (0)