regexp_test.go 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489
  1. package reference
  2. import (
  3. "regexp"
  4. "strings"
  5. "testing"
  6. )
  7. type regexpMatch struct {
  8. input string
  9. match bool
  10. subs []string
  11. }
  12. func checkRegexp(t *testing.T, r *regexp.Regexp, m regexpMatch) {
  13. matches := r.FindStringSubmatch(m.input)
  14. if m.match && matches != nil {
  15. if len(matches) != (r.NumSubexp()+1) || matches[0] != m.input {
  16. t.Fatalf("Bad match result %#v for %q", matches, m.input)
  17. }
  18. if len(matches) < (len(m.subs) + 1) {
  19. t.Errorf("Expected %d sub matches, only have %d for %q", len(m.subs), len(matches)-1, m.input)
  20. }
  21. for i := range m.subs {
  22. if m.subs[i] != matches[i+1] {
  23. t.Errorf("Unexpected submatch %d: %q, expected %q for %q", i+1, matches[i+1], m.subs[i], m.input)
  24. }
  25. }
  26. } else if m.match {
  27. t.Errorf("Expected match for %q", m.input)
  28. } else if matches != nil {
  29. t.Errorf("Unexpected match for %q", m.input)
  30. }
  31. }
  32. func TestHostRegexp(t *testing.T) {
  33. hostcases := []regexpMatch{
  34. {
  35. input: "test.com",
  36. match: true,
  37. },
  38. {
  39. input: "test.com:10304",
  40. match: true,
  41. },
  42. {
  43. input: "test.com:http",
  44. match: false,
  45. },
  46. {
  47. input: "localhost",
  48. match: true,
  49. },
  50. {
  51. input: "localhost:8080",
  52. match: true,
  53. },
  54. {
  55. input: "a",
  56. match: true,
  57. },
  58. {
  59. input: "a.b",
  60. match: true,
  61. },
  62. {
  63. input: "ab.cd.com",
  64. match: true,
  65. },
  66. {
  67. input: "a-b.com",
  68. match: true,
  69. },
  70. {
  71. input: "-ab.com",
  72. match: false,
  73. },
  74. {
  75. input: "ab-.com",
  76. match: false,
  77. },
  78. {
  79. input: "ab.c-om",
  80. match: true,
  81. },
  82. {
  83. input: "ab.-com",
  84. match: false,
  85. },
  86. {
  87. input: "ab.com-",
  88. match: false,
  89. },
  90. {
  91. input: "0101.com",
  92. match: true, // TODO(dmcgowan): valid if this should be allowed
  93. },
  94. {
  95. input: "001a.com",
  96. match: true,
  97. },
  98. {
  99. input: "b.gbc.io:443",
  100. match: true,
  101. },
  102. {
  103. input: "b.gbc.io",
  104. match: true,
  105. },
  106. {
  107. input: "xn--n3h.com", // ☃.com in punycode
  108. match: true,
  109. },
  110. {
  111. input: "Asdf.com", // uppercase character
  112. match: true,
  113. },
  114. }
  115. r := regexp.MustCompile(`^` + hostnameRegexp.String() + `$`)
  116. for i := range hostcases {
  117. checkRegexp(t, r, hostcases[i])
  118. }
  119. }
  120. func TestFullNameRegexp(t *testing.T) {
  121. if anchoredNameRegexp.NumSubexp() != 2 {
  122. t.Fatalf("anchored name regexp should have two submatches: %v, %v != 2",
  123. anchoredNameRegexp, anchoredNameRegexp.NumSubexp())
  124. }
  125. testcases := []regexpMatch{
  126. {
  127. input: "",
  128. match: false,
  129. },
  130. {
  131. input: "short",
  132. match: true,
  133. subs: []string{"", "short"},
  134. },
  135. {
  136. input: "simple/name",
  137. match: true,
  138. subs: []string{"simple", "name"},
  139. },
  140. {
  141. input: "library/ubuntu",
  142. match: true,
  143. subs: []string{"library", "ubuntu"},
  144. },
  145. {
  146. input: "docker/stevvooe/app",
  147. match: true,
  148. subs: []string{"docker", "stevvooe/app"},
  149. },
  150. {
  151. input: "aa/aa/aa/aa/aa/aa/aa/aa/aa/bb/bb/bb/bb/bb/bb",
  152. match: true,
  153. subs: []string{"aa", "aa/aa/aa/aa/aa/aa/aa/aa/bb/bb/bb/bb/bb/bb"},
  154. },
  155. {
  156. input: "aa/aa/bb/bb/bb",
  157. match: true,
  158. subs: []string{"aa", "aa/bb/bb/bb"},
  159. },
  160. {
  161. input: "a/a/a/a",
  162. match: true,
  163. subs: []string{"a", "a/a/a"},
  164. },
  165. {
  166. input: "a/a/a/a/",
  167. match: false,
  168. },
  169. {
  170. input: "a//a/a",
  171. match: false,
  172. },
  173. {
  174. input: "a",
  175. match: true,
  176. subs: []string{"", "a"},
  177. },
  178. {
  179. input: "a/aa",
  180. match: true,
  181. subs: []string{"a", "aa"},
  182. },
  183. {
  184. input: "a/aa/a",
  185. match: true,
  186. subs: []string{"a", "aa/a"},
  187. },
  188. {
  189. input: "foo.com",
  190. match: true,
  191. subs: []string{"", "foo.com"},
  192. },
  193. {
  194. input: "foo.com/",
  195. match: false,
  196. },
  197. {
  198. input: "foo.com:8080/bar",
  199. match: true,
  200. subs: []string{"foo.com:8080", "bar"},
  201. },
  202. {
  203. input: "foo.com:http/bar",
  204. match: false,
  205. },
  206. {
  207. input: "foo.com/bar",
  208. match: true,
  209. subs: []string{"foo.com", "bar"},
  210. },
  211. {
  212. input: "foo.com/bar/baz",
  213. match: true,
  214. subs: []string{"foo.com", "bar/baz"},
  215. },
  216. {
  217. input: "localhost:8080/bar",
  218. match: true,
  219. subs: []string{"localhost:8080", "bar"},
  220. },
  221. {
  222. input: "sub-dom1.foo.com/bar/baz/quux",
  223. match: true,
  224. subs: []string{"sub-dom1.foo.com", "bar/baz/quux"},
  225. },
  226. {
  227. input: "blog.foo.com/bar/baz",
  228. match: true,
  229. subs: []string{"blog.foo.com", "bar/baz"},
  230. },
  231. {
  232. input: "a^a",
  233. match: false,
  234. },
  235. {
  236. input: "aa/asdf$$^/aa",
  237. match: false,
  238. },
  239. {
  240. input: "asdf$$^/aa",
  241. match: false,
  242. },
  243. {
  244. input: "aa-a/a",
  245. match: true,
  246. subs: []string{"aa-a", "a"},
  247. },
  248. {
  249. input: strings.Repeat("a/", 128) + "a",
  250. match: true,
  251. subs: []string{"a", strings.Repeat("a/", 127) + "a"},
  252. },
  253. {
  254. input: "a-/a/a/a",
  255. match: false,
  256. },
  257. {
  258. input: "foo.com/a-/a/a",
  259. match: false,
  260. },
  261. {
  262. input: "-foo/bar",
  263. match: false,
  264. },
  265. {
  266. input: "foo/bar-",
  267. match: false,
  268. },
  269. {
  270. input: "foo-/bar",
  271. match: false,
  272. },
  273. {
  274. input: "foo/-bar",
  275. match: false,
  276. },
  277. {
  278. input: "_foo/bar",
  279. match: false,
  280. },
  281. {
  282. input: "foo_bar",
  283. match: true,
  284. subs: []string{"", "foo_bar"},
  285. },
  286. {
  287. input: "foo_bar.com",
  288. match: true,
  289. subs: []string{"", "foo_bar.com"},
  290. },
  291. {
  292. input: "foo_bar.com:8080",
  293. match: false,
  294. },
  295. {
  296. input: "foo_bar.com:8080/app",
  297. match: false,
  298. },
  299. {
  300. input: "foo.com/foo_bar",
  301. match: true,
  302. subs: []string{"foo.com", "foo_bar"},
  303. },
  304. {
  305. input: "____/____",
  306. match: false,
  307. },
  308. {
  309. input: "_docker/_docker",
  310. match: false,
  311. },
  312. {
  313. input: "docker_/docker_",
  314. match: false,
  315. },
  316. {
  317. input: "b.gcr.io/test.example.com/my-app",
  318. match: true,
  319. subs: []string{"b.gcr.io", "test.example.com/my-app"},
  320. },
  321. {
  322. input: "xn--n3h.com/myimage", // ☃.com in punycode
  323. match: true,
  324. subs: []string{"xn--n3h.com", "myimage"},
  325. },
  326. {
  327. input: "xn--7o8h.com/myimage", // 🐳.com in punycode
  328. match: true,
  329. subs: []string{"xn--7o8h.com", "myimage"},
  330. },
  331. {
  332. input: "example.com/xn--7o8h.com/myimage", // 🐳.com in punycode
  333. match: true,
  334. subs: []string{"example.com", "xn--7o8h.com/myimage"},
  335. },
  336. {
  337. input: "example.com/some_separator__underscore/myimage",
  338. match: true,
  339. subs: []string{"example.com", "some_separator__underscore/myimage"},
  340. },
  341. {
  342. input: "example.com/__underscore/myimage",
  343. match: false,
  344. },
  345. {
  346. input: "example.com/..dots/myimage",
  347. match: false,
  348. },
  349. {
  350. input: "example.com/.dots/myimage",
  351. match: false,
  352. },
  353. {
  354. input: "example.com/nodouble..dots/myimage",
  355. match: false,
  356. },
  357. {
  358. input: "example.com/nodouble..dots/myimage",
  359. match: false,
  360. },
  361. {
  362. input: "docker./docker",
  363. match: false,
  364. },
  365. {
  366. input: ".docker/docker",
  367. match: false,
  368. },
  369. {
  370. input: "docker-/docker",
  371. match: false,
  372. },
  373. {
  374. input: "-docker/docker",
  375. match: false,
  376. },
  377. {
  378. input: "do..cker/docker",
  379. match: false,
  380. },
  381. {
  382. input: "do__cker:8080/docker",
  383. match: false,
  384. },
  385. {
  386. input: "do__cker/docker",
  387. match: true,
  388. subs: []string{"", "do__cker/docker"},
  389. },
  390. {
  391. input: "b.gcr.io/test.example.com/my-app",
  392. match: true,
  393. subs: []string{"b.gcr.io", "test.example.com/my-app"},
  394. },
  395. {
  396. input: "registry.io/foo/project--id.module--name.ver---sion--name",
  397. match: true,
  398. subs: []string{"registry.io", "foo/project--id.module--name.ver---sion--name"},
  399. },
  400. {
  401. input: "Asdf.com/foo/bar", // uppercase character in hostname
  402. match: true,
  403. },
  404. {
  405. input: "Foo/FarB", // uppercase characters in remote name
  406. match: false,
  407. },
  408. }
  409. for i := range testcases {
  410. checkRegexp(t, anchoredNameRegexp, testcases[i])
  411. }
  412. }
  413. func TestReferenceRegexp(t *testing.T) {
  414. if ReferenceRegexp.NumSubexp() != 3 {
  415. t.Fatalf("anchored name regexp should have three submatches: %v, %v != 3",
  416. ReferenceRegexp, ReferenceRegexp.NumSubexp())
  417. }
  418. testcases := []regexpMatch{
  419. {
  420. input: "registry.com:8080/myapp:tag",
  421. match: true,
  422. subs: []string{"registry.com:8080/myapp", "tag", ""},
  423. },
  424. {
  425. input: "registry.com:8080/myapp@sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912",
  426. match: true,
  427. subs: []string{"registry.com:8080/myapp", "", "sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912"},
  428. },
  429. {
  430. input: "registry.com:8080/myapp:tag2@sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912",
  431. match: true,
  432. subs: []string{"registry.com:8080/myapp", "tag2", "sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912"},
  433. },
  434. {
  435. input: "registry.com:8080/myapp@sha256:badbadbadbad",
  436. match: false,
  437. },
  438. {
  439. input: "registry.com:8080/myapp:invalid~tag",
  440. match: false,
  441. },
  442. {
  443. input: "bad_hostname.com:8080/myapp:tag",
  444. match: false,
  445. },
  446. {
  447. input:// localhost treated as name, missing tag with 8080 as tag
  448. "localhost:8080@sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912",
  449. match: true,
  450. subs: []string{"localhost", "8080", "sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912"},
  451. },
  452. {
  453. input: "localhost:8080/name@sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912",
  454. match: true,
  455. subs: []string{"localhost:8080/name", "", "sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912"},
  456. },
  457. {
  458. input: "localhost:http/name@sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912",
  459. match: false,
  460. },
  461. {
  462. // localhost will be treated as an image name without a host
  463. input: "localhost@sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912",
  464. match: true,
  465. subs: []string{"localhost", "", "sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912"},
  466. },
  467. {
  468. input: "registry.com:8080/myapp@bad",
  469. match: false,
  470. },
  471. {
  472. input: "registry.com:8080/myapp@2bad",
  473. match: false, // TODO(dmcgowan): Support this as valid
  474. },
  475. }
  476. for i := range testcases {
  477. checkRegexp(t, ReferenceRegexp, testcases[i])
  478. }
  479. }