test_core_strings.odin 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. package test_core_strings
  2. import "base:runtime"
  3. import "core:mem"
  4. import "core:strings"
  5. import "core:testing"
  6. import "core:unicode/utf8"
  7. @test
  8. test_index_any_small_string_not_found :: proc(t: ^testing.T) {
  9. index := strings.index_any(".", "/:\"")
  10. testing.expect(t, index == -1, "index_any should be negative")
  11. }
  12. @test
  13. test_index_any_larger_string_not_found :: proc(t: ^testing.T) {
  14. index := strings.index_any("aaaaaaaa.aaaaaaaa", "/:\"")
  15. testing.expect(t, index == -1, "index_any should be negative")
  16. }
  17. @test
  18. test_index_any_small_string_found :: proc(t: ^testing.T) {
  19. index := strings.index_any(".", "/:.\"")
  20. testing.expect(t, index == 0, "index_any should be 0")
  21. }
  22. @test
  23. test_index_any_larger_string_found :: proc(t: ^testing.T) {
  24. index := strings.index_any("aaaaaaaa:aaaaaaaa", "/:\"")
  25. testing.expect(t, index == 8, "index_any should be 8")
  26. }
  27. @test
  28. test_last_index_any_small_string_found :: proc(t: ^testing.T) {
  29. index := strings.last_index_any(".", "/:.\"")
  30. testing.expect(t, index == 0, "last_index_any should be 0")
  31. }
  32. @test
  33. test_last_index_any_small_string_not_found :: proc(t: ^testing.T) {
  34. index := strings.last_index_any(".", "/:\"")
  35. testing.expect(t, index == -1, "last_index_any should be -1")
  36. }
  37. @test
  38. test_index_multi_overlapping_substrs :: proc(t: ^testing.T) {
  39. index, width := strings.index_multi("some example text", {"ample", "exam"})
  40. testing.expect_value(t, index, 5)
  41. testing.expect_value(t, width, 4)
  42. }
  43. @test
  44. test_index_multi_not_found :: proc(t: ^testing.T) {
  45. index, _ := strings.index_multi("some example text", {"ey", "tey"})
  46. testing.expect_value(t, index, -1)
  47. }
  48. @test
  49. test_index_multi_with_empty_string :: proc(t: ^testing.T) {
  50. index, _ := strings.index_multi("some example text", {"ex", ""})
  51. testing.expect_value(t, index, -1)
  52. }
  53. Cut_Test :: struct {
  54. input: string,
  55. offset: int,
  56. length: int,
  57. output: string,
  58. }
  59. cut_tests :: []Cut_Test{
  60. {"some example text", 0, 0, "some example text" },
  61. {"some example text", 0, 4, "some" },
  62. {"some example text", 2, 2, "me" },
  63. {"some example text", 5, 7, "example" },
  64. {"some example text", 5, 0, "example text" },
  65. {"恥ずべきフクロウ", 0, 0, "恥ずべきフクロウ" },
  66. {"恥ずべきフクロウ", 4, 0, "フクロウ" },
  67. }
  68. @test
  69. test_cut :: proc(t: ^testing.T) {
  70. for test in cut_tests {
  71. res := strings.cut(test.input, test.offset, test.length)
  72. testing.expectf(
  73. t,
  74. res == test.output,
  75. "cut(\"%v\", %v, %v) expected to return \"%v\", got \"%v\"",
  76. test.input, test.offset, test.length, test.output, res,
  77. )
  78. }
  79. }
  80. Case_Kind :: enum {
  81. Lower_Space_Case,
  82. Upper_Space_Case,
  83. Lower_Snake_Case,
  84. Upper_Snake_Case,
  85. Lower_Kebab_Case,
  86. Upper_Kebab_Case,
  87. Camel_Case,
  88. Pascal_Case,
  89. Ada_Case,
  90. }
  91. Case_Proc :: proc(r: string, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error)
  92. test_cases := [Case_Kind]struct{s: string, p: Case_Proc}{
  93. .Lower_Space_Case = {"hellope world", to_lower_space_case},
  94. .Upper_Space_Case = {"HELLOPE WORLD", to_upper_space_case},
  95. .Lower_Snake_Case = {"hellope_world", to_snake_case},
  96. .Upper_Snake_Case = {"HELLOPE_WORLD", to_upper_snake_case},
  97. .Lower_Kebab_Case = {"hellope-world", to_kebab_case},
  98. .Upper_Kebab_Case = {"HELLOPE-WORLD", to_upper_kebab_case},
  99. .Camel_Case = {"hellopeWorld", to_camel_case},
  100. .Pascal_Case = {"HellopeWorld", to_pascal_case},
  101. .Ada_Case = {"Hellope_World", to_ada_case},
  102. }
  103. to_lower_space_case :: proc(r: string, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) {
  104. return strings.to_delimiter_case(r, ' ', false, allocator)
  105. }
  106. to_upper_space_case :: proc(r: string, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) {
  107. return strings.to_delimiter_case(r, ' ', true, allocator)
  108. }
  109. // NOTE: we have these wrappers as having #optional_allocator_error changes the type to not be equivalent
  110. to_snake_case :: proc(r: string, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { return strings.to_snake_case(r, allocator) }
  111. to_upper_snake_case :: proc(r: string, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { return strings.to_upper_snake_case(r, allocator) }
  112. to_kebab_case :: proc(r: string, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { return strings.to_kebab_case(r, allocator) }
  113. to_upper_kebab_case :: proc(r: string, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { return strings.to_upper_kebab_case(r, allocator) }
  114. to_camel_case :: proc(r: string, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { return strings.to_camel_case(r, allocator) }
  115. to_pascal_case :: proc(r: string, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { return strings.to_pascal_case(r, allocator) }
  116. to_ada_case :: proc(r: string, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { return strings.to_ada_case(r, allocator) }
  117. @test
  118. test_case_conversion :: proc(t: ^testing.T) {
  119. for entry in test_cases {
  120. for test_case, case_kind in test_cases {
  121. result, err := entry.p(test_case.s, context.allocator)
  122. testing.expectf(t, err == nil, "ERROR: We got the allocation error '{}'\n", err)
  123. defer delete(result)
  124. testing.expectf(t, result == entry.s, "ERROR: Input `{}` to converter {} does not match `{}`, got `{}`.\n", test_case.s, case_kind, entry.s, result)
  125. }
  126. }
  127. }
  128. @(test)
  129. test_substring :: proc(t: ^testing.T) {
  130. Case :: struct {
  131. s: string,
  132. start: int,
  133. end: int,
  134. sub: string,
  135. ok: bool,
  136. }
  137. cases := []Case {
  138. {ok = true},
  139. {s = "", start = -1, ok = false},
  140. {s = "", end = -1, ok = false},
  141. {s = "", end = +1, ok = false},
  142. {s = "Hello", end = len("Hello"), sub = "Hello", ok = true},
  143. {s = "Hello", start = 1, end = len("Hello"), sub = "ello", ok = true},
  144. {s = "Hello", start = 1, end = len("Hello") - 1, sub = "ell", ok = true},
  145. {s = "Hello", end = len("Hello") + 1, sub = "Hello", ok = false},
  146. {s = "小猫咪", start = 0, end = 3, sub = "小猫咪", ok = true},
  147. {s = "小猫咪", start = 1, end = 3, sub = "猫咪", ok = true},
  148. {s = "小猫咪", start = 1, end = 5, sub = "猫咪", ok = false},
  149. {s = "小猫咪", start = 1, end = 1, sub = "", ok = true},
  150. }
  151. for tc in cases {
  152. sub, ok := strings.substring(tc.s, tc.start, tc.end)
  153. testing.expectf(t, ok == tc.ok, "expected %v[%v:%v] to return ok: %v", tc.s, tc.start, tc.end, tc.ok)
  154. testing.expectf(t, sub == tc.sub, "expected %v[%v:%v] to return sub: %v, got: %v", tc.s, tc.start, tc.end, tc.sub, sub)
  155. }
  156. }
  157. @test
  158. test_builder_to_cstring_with_nil_allocator :: proc(t: ^testing.T) {
  159. b := strings.builder_make_none(mem.nil_allocator())
  160. cstr, err := strings.to_cstring(&b)
  161. testing.expect_value(t, cstr, nil)
  162. testing.expect_value(t, err, mem.Allocator_Error.Out_Of_Memory)
  163. }
  164. @test
  165. test_builder_to_cstring :: proc(t: ^testing.T) {
  166. buf: [8]byte
  167. a: mem.Arena
  168. mem.arena_init(&a, buf[:])
  169. b := strings.builder_make_none(mem.arena_allocator(&a))
  170. {
  171. cstr, err := strings.to_cstring(&b)
  172. testing.expectf(t, cstr != nil, "expected cstr to not be nil, got %v", cstr)
  173. testing.expect_value(t, err, nil)
  174. }
  175. n := strings.write_byte(&b, 'a')
  176. testing.expect(t, n == 1)
  177. {
  178. cstr, err := strings.to_cstring(&b)
  179. testing.expectf(t, cstr != nil, "expected cstr to not be nil, got %v", cstr)
  180. testing.expect_value(t, err, nil)
  181. }
  182. n = strings.write_string(&b, "aaaaaaa")
  183. testing.expect(t, n == 7)
  184. {
  185. cstr, err := strings.to_cstring(&b)
  186. testing.expect(t, cstr == nil)
  187. testing.expect(t, err == .Out_Of_Memory)
  188. }
  189. }
  190. @test
  191. test_prefix_length :: proc(t: ^testing.T) {
  192. prefix_length :: proc "contextless" (a, b: string) -> (n: int) {
  193. _len := min(len(a), len(b))
  194. // Scan for matches including partial codepoints.
  195. #no_bounds_check for n < _len && a[n] == b[n] {
  196. n += 1
  197. }
  198. // Now scan to ignore partial codepoints.
  199. if n > 0 {
  200. s := a[:n]
  201. n = 0
  202. for {
  203. r0, w := utf8.decode_rune(s[n:])
  204. if r0 != utf8.RUNE_ERROR {
  205. n += w
  206. } else {
  207. break
  208. }
  209. }
  210. }
  211. return
  212. }
  213. cases := [][2]string{
  214. {"Hellope, there!", "Hellope, world!"},
  215. {"Hellope, there!", "Foozle"},
  216. {"Hellope, there!", "Hell"},
  217. {"Hellope! 🦉", "Hellope! 🦉"},
  218. }
  219. for v in cases {
  220. p_scalar := prefix_length(v[0], v[1])
  221. p_simd := strings.prefix_length(v[0], v[1])
  222. testing.expect_value(t, p_simd, p_scalar)
  223. s := v[0]
  224. for len(s) > 0 {
  225. p_scalar = prefix_length(v[0], s)
  226. p_simd = strings.prefix_length(v[0], s)
  227. testing.expect_value(t, p_simd, p_scalar)
  228. s = s[:len(s) - 1]
  229. }
  230. }
  231. }