You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

150 lines
4.6 KiB

  1. // Copyright 2017 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. // Package uid supports generating unique IDs. Its chief purpose is to prevent
  15. // multiple test executions from interfering with each other, and to facilitate
  16. // cleanup of old entities that may remain if tests exit early.
  17. package uid
  18. import (
  19. "fmt"
  20. "regexp"
  21. "strconv"
  22. "sync/atomic"
  23. "time"
  24. )
  25. // A Space manages a set of unique IDs distinguished by a prefix.
  26. type Space struct {
  27. Prefix string // Prefix of UIDs. Read-only.
  28. Sep rune // Separates UID parts. Read-only.
  29. Time time.Time // Timestamp for UIDs. Read-only.
  30. re *regexp.Regexp
  31. count int32 // atomic
  32. short bool
  33. }
  34. // Options are optional values for a Space.
  35. type Options struct {
  36. Sep rune // Separates parts of the UID. Defaults to '-'.
  37. Time time.Time // Timestamp for all UIDs made with this space. Defaults to current time.
  38. // Short, if true, makes the result of space.New shorter by 6 characters.
  39. // This can be useful for character restricted IDs. It will use a shorter
  40. // but less readable time representation, and will only use two characters
  41. // for the count suffix instead of four.
  42. //
  43. // e.x. normal: gotest-20181030-59751273685000-0001
  44. // e.x. short: gotest-1540917351273685000-01
  45. Short bool
  46. }
  47. // NewSpace creates a new UID space. A UID Space is used to generate unique IDs.
  48. func NewSpace(prefix string, opts *Options) *Space {
  49. var short bool
  50. sep := '-'
  51. tm := time.Now().UTC()
  52. if opts != nil {
  53. short = opts.Short
  54. if opts.Sep != 0 {
  55. sep = opts.Sep
  56. }
  57. if !opts.Time.IsZero() {
  58. tm = opts.Time
  59. }
  60. }
  61. var re string
  62. if short {
  63. re = fmt.Sprintf(`^%s%[2]c(\d+)%[2]c\d+$`, regexp.QuoteMeta(prefix), sep)
  64. } else {
  65. re = fmt.Sprintf(`^%s%[2]c(\d{4})(\d{2})(\d{2})%[2]c(\d+)%[2]c\d+$`,
  66. regexp.QuoteMeta(prefix), sep)
  67. }
  68. return &Space{
  69. Prefix: prefix,
  70. Sep: sep,
  71. Time: tm,
  72. re: regexp.MustCompile(re),
  73. short: short,
  74. }
  75. }
  76. // New generates a new unique ID. The ID consists of the Space's prefix, a
  77. // timestamp, and a counter value. All unique IDs generated in the same test
  78. // execution will have the same timestamp.
  79. //
  80. // Aside from the characters in the prefix, IDs contain only letters, numbers
  81. // and sep.
  82. func (s *Space) New() string {
  83. c := atomic.AddInt32(&s.count, 1)
  84. if s.short && c > 99 {
  85. // Short spaces only have space for 99 IDs. (two characters)
  86. panic("Short space called New more than 99 times. Ran out of IDs.")
  87. } else if c > 9999 {
  88. // Spaces only have space for 9999 IDs. (four characters)
  89. panic("New called more than 9999 times. Ran out of IDs.")
  90. }
  91. if s.short {
  92. return fmt.Sprintf("%s%c%d%c%02d", s.Prefix, s.Sep, s.Time.UnixNano(), s.Sep, c)
  93. }
  94. // Write the time as a date followed by nanoseconds from midnight of that date.
  95. // That makes it easier to see the approximate time of the ID when it is displayed.
  96. y, m, d := s.Time.Date()
  97. ns := s.Time.Sub(time.Date(y, m, d, 0, 0, 0, 0, time.UTC))
  98. // Zero-pad the counter for lexical sort order for IDs with the same timestamp.
  99. return fmt.Sprintf("%s%c%04d%02d%02d%c%d%c%04d",
  100. s.Prefix, s.Sep, y, m, d, s.Sep, ns, s.Sep, c)
  101. }
  102. // Timestamp extracts the timestamp of uid, which must have been generated by
  103. // s. The second return value is true on success, false if there was a problem.
  104. func (s *Space) Timestamp(uid string) (time.Time, bool) {
  105. subs := s.re.FindStringSubmatch(uid)
  106. if subs == nil {
  107. return time.Time{}, false
  108. }
  109. if s.short {
  110. ns, err := strconv.ParseInt(subs[1], 10, 64)
  111. if err != nil {
  112. return time.Time{}, false
  113. }
  114. return time.Unix(ns/1e9, ns%1e9), true
  115. }
  116. y, err1 := strconv.Atoi(subs[1])
  117. m, err2 := strconv.Atoi(subs[2])
  118. d, err3 := strconv.Atoi(subs[3])
  119. ns, err4 := strconv.Atoi(subs[4])
  120. if err1 != nil || err2 != nil || err3 != nil || err4 != nil {
  121. return time.Time{}, false
  122. }
  123. return time.Date(y, time.Month(m), d, 0, 0, 0, ns, time.UTC), true
  124. }
  125. // Older reports whether uid was created by m and has a timestamp older than
  126. // the current time by at least d.
  127. func (s *Space) Older(uid string, d time.Duration) bool {
  128. ts, ok := s.Timestamp(uid)
  129. if !ok {
  130. return false
  131. }
  132. return time.Since(ts) > d
  133. }