1+ /*
2+ * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
3+ */
4+
5+ package kotlinx.rpc.protobuf.test
6+
7+ import invoke
8+ import kotlinx.rpc.protobuf.test.invoke
9+ import test.submsg.Other
10+ import test.submsg.invoke
11+ import kotlin.collections.iterator
12+ import kotlin.test.Test
13+ import kotlin.test.assertEquals
14+ import kotlin.test.assertNull
15+ import kotlin.test.assertTrue
16+
17+ class CopyTest {
18+
19+ @Test
20+ fun `test list copy - should be real copy` () {
21+ val userList = mutableListOf (1 ,2 ,3 )
22+ val original = Repeated {
23+ listInt32 = userList
24+ }
25+ val copy = original.copy()
26+ userList.add(4 )
27+
28+ assertEquals(listOf (1 ,2 ,3 ,4 ), original.listInt32)
29+ assertEquals(listOf (1 ,2 ,3 ), copy.listInt32)
30+ }
31+
32+ @Test
33+ fun `copy primitives and bytes - equality and independence` () {
34+ val bytesSrc = byteArrayOf(1 , 2 , 3 )
35+ val msg = AllPrimitives {
36+ int32 = 42
37+ int64 = 123L
38+ uint32 = 7u
39+ uint64 = 9uL
40+ sint32 = - 5
41+ sint64 = - 7L
42+ fixed32 = 11u
43+ fixed64 = 13uL
44+ sfixed32 = - 17
45+ sfixed64 = - 19L
46+ bool = true
47+ float = 2.5f
48+ double = 3.5
49+ string = " hello"
50+ bytes = bytesSrc
51+ }
52+
53+ val copy = msg.copy()
54+
55+ // verify all fields are copied
56+ assertEquals(msg, copy)
57+
58+ // bytes are copied
59+ assertEquals(byteArrayOf(1 , 2 , 3 ).toList(), msg.bytes?.toList())
60+ assertEquals(byteArrayOf(1 , 2 , 3 ).toList(), copy.bytes?.toList())
61+ }
62+
63+ @Test
64+ fun `copy repeated messages - deep copy of list and elements` () {
65+ val orig = Repeated {
66+ listMessage = listOf (
67+ Repeated .Other { a = 1 },
68+ Repeated .Other { a = 2 }
69+ )
70+ }
71+ val cp = orig.copy()
72+
73+ // Lists are distinct instances
74+ assertTrue(orig.listMessage != = cp.listMessage)
75+ // Elements are deep-copied
76+ for (i in orig.listMessage.indices) {
77+ assertEquals(orig.listMessage[i].a, cp.listMessage[i].a)
78+ assertTrue(orig.listMessage[i] != = cp.listMessage[i])
79+ }
80+
81+ // Modify copy via copy { } and ensure original unaffected
82+ val mutated = orig.copy {
83+ listMessage = listMessage + Repeated .Other { a = 3 }
84+ }
85+ assertEquals(listOf (1 , 2 ), orig.listMessage.map { it.a })
86+ assertEquals(listOf (1 , 2 , 3 ), mutated.listMessage.map { it.a })
87+ }
88+
89+ @Test
90+ fun `copy maps - deep copy keys and values` () {
91+ val original = TestMap {
92+ primitives = mapOf (" a" to 10L , " b" to 20L )
93+ messages = mapOf (1 to PresenceCheck { RequiredPresence = 1 }, 2 to PresenceCheck { RequiredPresence = 2 })
94+ }
95+
96+ val copy = original.copy()
97+
98+ // verify maps are copied
99+ assertEquals(mapOf (" a" to 10L , " b" to 20L ), original.primitives)
100+ assertEquals(mapOf (" a" to 10L , " b" to 20L ), copy.primitives)
101+
102+ // deep copy for message map values
103+ for ((k, v) in original.messages) {
104+ val cv = copy.messages.getValue(k)
105+ assertEquals(v.RequiredPresence , cv.RequiredPresence )
106+ assertTrue(v != = cv)
107+ }
108+
109+ // copy { } change and ensure isolation
110+ val mutated = original.copy { primitives = primitives + (" z" to 90L ) }
111+ assertEquals(setOf (" a" ," b" ), original.primitives.keys)
112+ assertEquals(setOf (" a" ," b" ," z" ), mutated.primitives.keys)
113+ }
114+
115+ @Test
116+ fun `copy oneof - preserve active case and allow mutation in lambda` () {
117+ val o1 = OneOfMsg .Companion { field = OneOfMsg .Field .Sint (7 ) }
118+ val c1 = o1.copy()
119+ assertEquals(o1.field, c1.field)
120+
121+ // mutate with copy-lambda (switch case)
122+ val c2 = o1.copy { field = OneOfMsg .Field .Other (Other .Companion { arg1 = " x" }) }
123+ // original unaffected
124+ assertEquals(OneOfMsg .Field .Sint (7 ), o1.field)
125+ // new case set
126+ assertTrue(c2.field is OneOfMsg .Field .Other )
127+ }
128+
129+ @Test
130+ fun `copy nested sub-message - deep copy and lambda modification` () {
131+ val equals = Equals .Companion {
132+ str1 = " root"
133+ bytes1 = byteArrayOf(1 , 2 , 3 )
134+ someEnum2 = Equals .SomeEnum .VALUE1
135+ nested = Equals .Nested { content = " leaf" }
136+ }
137+ val copy = equals.copy()
138+ // deep copy of nested
139+ assertTrue(equals.nested != = copy.nested)
140+ assertEquals(equals.nested.content, copy.nested.content)
141+
142+ // modify nested in copy-lambda
143+ val mutated = equals.copy {
144+ nested = Equals .Nested { content = " mut" }
145+ }
146+ assertEquals(" leaf" , equals.nested.content)
147+ assertEquals(" mut" , mutated.nested.content)
148+ }
149+
150+ @Test
151+ fun `copy preserves presence and required fields` () {
152+ val p = PresenceCheck { RequiredPresence = 1 }
153+ val cp = p.copy()
154+ assertEquals(1 , cp.RequiredPresence )
155+ assertNull(cp.OptionalPresence )
156+
157+ val cp2 = p.copy { OptionalPresence = 5f }
158+ assertEquals(1 , cp2.RequiredPresence )
159+ assertEquals(5f , cp2.OptionalPresence )
160+ }
161+ }
0 commit comments