// RUN: %target-run-stdlib-swift | %FileCheck %s // REQUIRES: executable_test // REQUIRES: foundation // // Parts of this test depend on memory allocator specifics. The test // should be rewritten soon so it doesn't expose legacy components // like OpaqueString anyway, so we can just disable the failing // configuration // // Memory allocator specifics also vary across platforms. // REQUIRES: CPU=x86_64, OS=macosx // UNSUPPORTED: use_os_stdlib // TODO: Adapt capacity checks into a non-FileCheck more exhaustive test that // can run on older OSes and match their behavior... import Foundation import Swift func hex(_ x: UInt64) -> String { return String(x, radix:16) } func hexAddrVal(_ x: T) -> String { return "@0x" + hex(UInt64(unsafeBitCast(x, to: UInt.self))) } func repr(_ x: NSString) -> String { return "\(NSStringFromClass(object_getClass(x)!))\(hexAddrVal(x)) = \"\(x)\"" } func repr(_ x: _StringRepresentation) -> String { switch x._form { case ._small: return """ Small(count: \(x._count)) """ case ._cocoa(let object): return """ Cocoa(\ owner: \(hexAddrVal(object)), \ count: \(x._count)) """ case ._native(let object): return """ Native(\ owner: \(hexAddrVal(object)), \ count: \(x._count), \ capacity: \(x._capacity)) """ case ._immortal(_): return """ Unmanaged(count: \(x._count)) """ } } func repr(_ x: String) -> String { return "String(\(repr(x._classify()))) = \"\(x)\"" } // ===------- Appending -------=== // CHECK-LABEL: --- Appending --- print("--- Appending ---") var s = "⓪" // start non-empty // To make this test independent of the memory allocator implementation, // explicitly request initial capacity. s.reserveCapacity(16) // CHECK-NEXT: String(Native(owner: @[[storage0:[x0-9a-f]+]], count: 3, capacity: 31)) = "⓪" print("\(repr(s))") // CHECK-NEXT: String(Native(owner: @[[storage0]], count: 4, capacity: 31)) = "⓪1" s += "1" print("\(repr(s))") // CHECK-NEXT: String(Native(owner: @[[storage0]], count: 10, capacity: 31)) = "⓪1234567" s += "234567" print("\(repr(s))") // CHECK-NEXT: String(Native(owner: @[[storage0]], count: 11, capacity: 31)) = "⓪12345678" // CHECK-NOT: @[[storage0]], s += "8" print("\(repr(s))") // CHECK-NEXT: String(Native(owner: @[[storage0]], count: 18, capacity: 31)) = "⓪123456789012345" s += "9012345" print("\(repr(s))") // -- expect a reallocation here // CHECK-LABEL: (expecting reallocation) print("(expecting reallocation)") // Appending more than the next level of capacity only takes as much // as required. I'm not sure whether this is a great idea, but the // point is to prevent huge amounts of fragmentation when a long // string is appended to a short one. The question, of course, is // whether more appends are coming, in which case we should give it // more capacity. It might be better to always grow to a multiple of // the current capacity when the capacity is exceeded. // CHECK-NEXT: String(Native(owner: @[[storage1:[x0-9a-f]+]], count: 54, capacity: 63)) // CHECK-NOT: @[[storage1]], s += s + s print("\(repr(s))") // CHECK-NEXT: String(Native(owner: @[[storage1]], count: 55, capacity: 63)) s += "C" print("\(repr(s))") // -- expect a reallocation here // CHECK-LABEL: (expecting second reallocation) print("(expecting second reallocation)") // CHECK-NEXT: String(Native(owner: @[[storage2:[x0-9a-f]+]], count: 56, capacity: 63)) // CHECK-NOT: @[[storage1]], s += "C" print("\(repr(s))") // -- expect a reallocation here // CHECK-LABEL: (expecting third reallocation) print("(expecting third reallocation)") // FIXME: Now that test behavior depends on result of malloc_size, we should // rewrite this test for larger allocations. For now, disable the hard-coded // checks. // xCHECK-NEXT: String(Native(owner: @[[storage3:[x0-9a-f]+]], count: 72, capacity: 135)) // xCHECK-NOT: @[[storage2]], s += "1234567890123456" print("\(repr(s))") var s1 = s // xCHECK-NEXT: String(Native(owner: @[[storage3]], count: 72, capacity: 135)) print("\(repr(s1))") /// The use of later buffer capacity by another string forces /// reallocation; however, the original capacity is kept by intact // CHECK-LABEL: (expect copy to trigger reallocation without growth) print("(expect copy to trigger reallocation without growth)") // CHECK-NEXT: String(Native(owner: @[[storage4:[x0-9a-f]+]], count: 73, capacity: 87)) = "{{.*}}X" // xCHECK-NOT: @[[storage3]], s1 += "X" print("\(repr(s1))") /// The original copy is left unchanged // xCHECK-NEXT: String(Native(owner: @[[storage3]], count: 72, capacity: 135)) print("\(repr(s))") /// Appending to an empty string re-uses the RHS // xCHECK-NEXT: @[[storage3]], var s2 = String() s2 += s print("\(repr(s2))")