window_test.go 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838
  1. package kubecost
  2. import (
  3. "fmt"
  4. "strings"
  5. "testing"
  6. "time"
  7. "github.com/kubecost/cost-model/pkg/env"
  8. )
  9. func TestRoundBack(t *testing.T) {
  10. boulder := time.FixedZone("Boulder", -7*60*60)
  11. beijing := time.FixedZone("Beijing", 8*60*60)
  12. to := time.Date(2020, time.January, 1, 0, 0, 0, 0, boulder)
  13. tb := RoundBack(to, 24*time.Hour)
  14. if !tb.Equal(time.Date(2020, time.January, 1, 0, 0, 0, 0, boulder)) {
  15. t.Fatalf("RoundBack: expected 2020-01-01T00:00:00-07:00; actual %s", tb)
  16. }
  17. to = time.Date(2020, time.January, 1, 0, 0, 1, 0, boulder)
  18. tb = RoundBack(to, 24*time.Hour)
  19. if !tb.Equal(time.Date(2020, time.January, 1, 0, 0, 0, 0, boulder)) {
  20. t.Fatalf("RoundBack: expected 2020-01-01T00:00:00-07:00; actual %s", tb)
  21. }
  22. to = time.Date(2020, time.January, 1, 12, 37, 48, 0, boulder)
  23. tb = RoundBack(to, 24*time.Hour)
  24. if !tb.Equal(time.Date(2020, time.January, 1, 0, 0, 0, 0, boulder)) {
  25. t.Fatalf("RoundBack: expected 2020-01-01T00:00:00-07:00; actual %s", tb)
  26. }
  27. to = time.Date(2020, time.January, 1, 23, 37, 48, 0, boulder)
  28. tb = RoundBack(to, 24*time.Hour)
  29. if !tb.Equal(time.Date(2020, time.January, 1, 0, 0, 0, 0, boulder)) {
  30. t.Fatalf("RoundBack: expected 2020-01-01T00:00:00-07:00; actual %s", tb)
  31. }
  32. to = time.Date(2020, time.January, 1, 0, 0, 0, 0, beijing)
  33. tb = RoundBack(to, 24*time.Hour)
  34. if !tb.Equal(time.Date(2020, time.January, 1, 0, 0, 0, 0, beijing)) {
  35. t.Fatalf("RoundBack: expected 2020-01-01T00:00:00+08:00; actual %s", tb)
  36. }
  37. to = time.Date(2020, time.January, 1, 0, 0, 1, 0, beijing)
  38. tb = RoundBack(to, 24*time.Hour)
  39. if !tb.Equal(time.Date(2020, time.January, 1, 0, 0, 0, 0, beijing)) {
  40. t.Fatalf("RoundBack: expected 2020-01-01T00:00:00+08:00; actual %s", tb)
  41. }
  42. to = time.Date(2020, time.January, 1, 12, 37, 48, 0, beijing)
  43. tb = RoundBack(to, 24*time.Hour)
  44. if !tb.Equal(time.Date(2020, time.January, 1, 0, 0, 0, 0, beijing)) {
  45. t.Fatalf("RoundBack: expected 2020-01-01T00:00:00+08:00; actual %s", tb)
  46. }
  47. to = time.Date(2020, time.January, 1, 23, 59, 59, 0, beijing)
  48. tb = RoundBack(to, 24*time.Hour)
  49. if !tb.Equal(time.Date(2020, time.January, 1, 0, 0, 0, 0, beijing)) {
  50. t.Fatalf("RoundBack: expected 2020-01-01T00:00:00+08:00; actual %s", tb)
  51. }
  52. to = time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC)
  53. tb = RoundBack(to, 24*time.Hour)
  54. if !tb.Equal(time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC)) {
  55. t.Fatalf("RoundBack: expected 2020-01-01T00:00:00Z; actual %s", tb)
  56. }
  57. to = time.Date(2020, time.January, 1, 0, 0, 1, 0, time.UTC)
  58. tb = RoundBack(to, 24*time.Hour)
  59. if !tb.Equal(time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC)) {
  60. t.Fatalf("RoundBack: expected 2020-01-01T00:00:00Z; actual %s", tb)
  61. }
  62. to = time.Date(2020, time.January, 1, 12, 37, 48, 0, time.UTC)
  63. tb = RoundBack(to, 24*time.Hour)
  64. if !tb.Equal(time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC)) {
  65. t.Fatalf("RoundBack: expected 2020-01-01T00:00:00Z; actual %s", tb)
  66. }
  67. to = time.Date(2020, time.January, 1, 23, 59, 0, 0, time.UTC)
  68. tb = RoundBack(to, 24*time.Hour)
  69. if !tb.Equal(time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC)) {
  70. t.Fatalf("RoundBack: expected 2020-01-01T00:00:00Z; actual %s", tb)
  71. }
  72. }
  73. func TestRoundForward(t *testing.T) {
  74. boulder := time.FixedZone("Boulder", -7*60*60)
  75. beijing := time.FixedZone("Beijing", 8*60*60)
  76. to := time.Date(2020, time.January, 1, 0, 0, 0, 0, boulder)
  77. tb := RoundForward(to, 24*time.Hour)
  78. if !tb.Equal(time.Date(2020, time.January, 1, 0, 0, 0, 0, boulder)) {
  79. t.Fatalf("RoundForward: expected 2020-01-01T00:00:00-07:00; actual %s", tb)
  80. }
  81. to = time.Date(2020, time.January, 1, 0, 0, 1, 0, boulder)
  82. tb = RoundForward(to, 24*time.Hour)
  83. if !tb.Equal(time.Date(2020, time.January, 2, 0, 0, 0, 0, boulder)) {
  84. t.Fatalf("RoundForward: expected 2020-01-02T00:00:00-07:00; actual %s", tb)
  85. }
  86. to = time.Date(2020, time.January, 1, 12, 37, 48, 0, boulder)
  87. tb = RoundForward(to, 24*time.Hour)
  88. if !tb.Equal(time.Date(2020, time.January, 2, 0, 0, 0, 0, boulder)) {
  89. t.Fatalf("RoundForward: expected 2020-01-02T00:00:00-07:00; actual %s", tb)
  90. }
  91. to = time.Date(2020, time.January, 1, 23, 37, 48, 0, boulder)
  92. tb = RoundForward(to, 24*time.Hour)
  93. if !tb.Equal(time.Date(2020, time.January, 2, 0, 0, 0, 0, boulder)) {
  94. t.Fatalf("RoundForward: expected 2020-01-02T00:00:00-07:00; actual %s", tb)
  95. }
  96. to = time.Date(2020, time.January, 1, 0, 0, 0, 0, beijing)
  97. tb = RoundForward(to, 24*time.Hour)
  98. if !tb.Equal(time.Date(2020, time.January, 1, 0, 0, 0, 0, beijing)) {
  99. t.Fatalf("RoundForward: expected 2020-01-01T00:00:00+08:00; actual %s", tb)
  100. }
  101. to = time.Date(2020, time.January, 1, 0, 0, 1, 0, beijing)
  102. tb = RoundForward(to, 24*time.Hour)
  103. if !tb.Equal(time.Date(2020, time.January, 2, 0, 0, 0, 0, beijing)) {
  104. t.Fatalf("RoundForward: expected 2020-01-02T00:00:00+08:00; actual %s", tb)
  105. }
  106. to = time.Date(2020, time.January, 1, 12, 37, 48, 0, beijing)
  107. tb = RoundForward(to, 24*time.Hour)
  108. if !tb.Equal(time.Date(2020, time.January, 2, 0, 0, 0, 0, beijing)) {
  109. t.Fatalf("RoundForward: expected 2020-01-02T00:00:00+08:00; actual %s", tb)
  110. }
  111. to = time.Date(2020, time.January, 1, 23, 59, 59, 0, beijing)
  112. tb = RoundForward(to, 24*time.Hour)
  113. if !tb.Equal(time.Date(2020, time.January, 2, 0, 0, 0, 0, beijing)) {
  114. t.Fatalf("RoundForward: expected 2020-01-02T00:00:00+08:00; actual %s", tb)
  115. }
  116. to = time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC)
  117. tb = RoundForward(to, 24*time.Hour)
  118. if !tb.Equal(time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC)) {
  119. t.Fatalf("RoundForward: expected 2020-01-01T00:00:00Z; actual %s", tb)
  120. }
  121. to = time.Date(2020, time.January, 1, 0, 0, 1, 0, time.UTC)
  122. tb = RoundForward(to, 24*time.Hour)
  123. if !tb.Equal(time.Date(2020, time.January, 2, 0, 0, 0, 0, time.UTC)) {
  124. t.Fatalf("RoundForward: expected 2020-01-02T00:00:00Z; actual %s", tb)
  125. }
  126. to = time.Date(2020, time.January, 1, 12, 37, 48, 0, time.UTC)
  127. tb = RoundForward(to, 24*time.Hour)
  128. if !tb.Equal(time.Date(2020, time.January, 2, 0, 0, 0, 0, time.UTC)) {
  129. t.Fatalf("RoundForward: expected 2020-01-02T00:00:00Z; actual %s", tb)
  130. }
  131. to = time.Date(2020, time.January, 1, 23, 59, 0, 0, time.UTC)
  132. tb = RoundForward(to, 24*time.Hour)
  133. if !tb.Equal(time.Date(2020, time.January, 2, 0, 0, 0, 0, time.UTC)) {
  134. t.Fatalf("RoundForward: expected 2020-01-02T00:00:00Z; actual %s", tb)
  135. }
  136. }
  137. func TestParseWindowUTC(t *testing.T) {
  138. now := time.Now().UTC()
  139. // "today" should span Now() and not produce an error
  140. today, err := ParseWindowUTC("today")
  141. if err != nil {
  142. t.Fatalf(`unexpected error parsing "today": %s`, err)
  143. }
  144. if today.Duration().Hours() != 24 {
  145. t.Fatalf(`expect: window "today" to have duration 24 hour; actual: %f hours`, today.Duration().Hours())
  146. }
  147. if !today.Contains(time.Now().UTC()) {
  148. t.Fatalf(`expect: window "today" to contain now; actual: %s`, today)
  149. }
  150. // "yesterday" should span Now() and not produce an error
  151. yesterday, err := ParseWindowUTC("yesterday")
  152. if err != nil {
  153. t.Fatalf(`unexpected error parsing "yesterday": %s`, err)
  154. }
  155. if yesterday.Duration().Hours() != 24 {
  156. t.Fatalf(`expect: window "yesterday" to have duration 24 hour; actual: %f hours`, yesterday.Duration().Hours())
  157. }
  158. if !yesterday.End().Before(time.Now().UTC()) {
  159. t.Fatalf(`expect: window "yesterday" to end before now; actual: %s ends after %s`, yesterday, time.Now().UTC())
  160. }
  161. week, err := ParseWindowUTC("week")
  162. hoursThisWeek := float64(time.Now().UTC().Weekday()) * 24.0
  163. if err != nil {
  164. t.Fatalf(`unexpected error parsing "week": %s`, err)
  165. }
  166. if week.Duration().Hours() < hoursThisWeek {
  167. t.Fatalf(`expect: window "week" to have at least %f hours; actual: %f hours`, hoursThisWeek, week.Duration().Hours())
  168. }
  169. if week.End().After(time.Now().UTC()) {
  170. t.Fatalf(`expect: window "week" to end before now; actual: %s ends after %s`, week, time.Now().UTC())
  171. }
  172. month, err := ParseWindowUTC("month")
  173. hoursThisMonth := float64(time.Now().UTC().Day()) * 24.0
  174. if err != nil {
  175. t.Fatalf(`unexpected error parsing "month": %s`, err)
  176. }
  177. if month.Duration().Hours() > hoursThisMonth || month.Duration().Hours() < (hoursThisMonth-24.0) {
  178. t.Fatalf(`expect: window "month" to have approximately %f hours; actual: %f hours`, hoursThisMonth, month.Duration().Hours())
  179. }
  180. if !month.End().Before(time.Now().UTC()) {
  181. t.Fatalf(`expect: window "month" to end before now; actual: %s ends after %s`, month, time.Now().UTC())
  182. }
  183. // TODO lastweek
  184. lastmonth, err := ParseWindowUTC("lastmonth")
  185. monthMinHours := float64(24 * 28)
  186. monthMaxHours := float64(24 * 31)
  187. firstOfMonth := now.Truncate(time.Hour * 24).Add(-24 * time.Hour * time.Duration(now.Day()-1))
  188. if err != nil {
  189. t.Fatalf(`unexpected error parsing "lastmonth": %s`, err)
  190. }
  191. if lastmonth.Duration().Hours() > monthMaxHours || lastmonth.Duration().Hours() < monthMinHours {
  192. t.Fatalf(`expect: window "lastmonth" to have approximately %f hours; actual: %f hours`, hoursThisMonth, lastmonth.Duration().Hours())
  193. }
  194. if !lastmonth.End().Equal(firstOfMonth) {
  195. t.Fatalf(`expect: window "lastmonth" to end on the first of the current month; actual: %s doesn't end on %s`, lastmonth, firstOfMonth)
  196. }
  197. ago12h := time.Now().UTC().Add(-12 * time.Hour)
  198. ago36h := time.Now().UTC().Add(-36 * time.Hour)
  199. ago60h := time.Now().UTC().Add(-60 * time.Hour)
  200. // "24h" should have 24 hour duration and not produce an error
  201. dur24h, err := ParseWindowUTC("24h")
  202. if err != nil {
  203. t.Fatalf(`unexpected error parsing "24h": %s`, err)
  204. }
  205. if dur24h.Duration().Hours() != 24 {
  206. t.Fatalf(`expect: window "24h" to have duration 24 hour; actual: %f hours`, dur24h.Duration().Hours())
  207. }
  208. if !dur24h.Contains(ago12h) {
  209. t.Fatalf(`expect: window "24h" to contain 12 hours ago; actual: %s doesn't contain %s`, dur24h, ago12h)
  210. }
  211. if dur24h.Contains(ago36h) {
  212. t.Fatalf(`expect: window "24h" to not contain 36 hours ago; actual: %s contains %s`, dur24h, ago36h)
  213. }
  214. // "2d" should have 2 day duration and not produce an error
  215. dur2d, err := ParseWindowUTC("2d")
  216. if err != nil {
  217. t.Fatalf(`unexpected error parsing "2d": %s`, err)
  218. }
  219. if dur2d.Duration().Hours() != 48 {
  220. t.Fatalf(`expect: window "2d" to have duration 48 hour; actual: %f hours`, dur2d.Duration().Hours())
  221. }
  222. if !dur2d.Contains(ago36h) {
  223. t.Fatalf(`expect: window "2d" to contain 36 hours ago; actual: %s doesn't contain %s`, dur2d, ago36h)
  224. }
  225. if dur2d.Contains(ago60h) {
  226. t.Fatalf(`expect: window "2d" to not contain 60 hours ago; actual: %s contains %s`, dur2d, ago60h)
  227. }
  228. // "24h offset 14h" should have 24 hour duration and not produce an error
  229. dur24hOff14h, err := ParseWindowUTC("24h offset 14h")
  230. if err != nil {
  231. t.Fatalf(`unexpected error parsing "24h offset 14h": %s`, err)
  232. }
  233. if dur24hOff14h.Duration().Hours() != 24 {
  234. t.Fatalf(`expect: window "24h offset 14h" to have duration 24 hour; actual: %f hours`, dur24hOff14h.Duration().Hours())
  235. }
  236. if dur24hOff14h.Contains(ago12h) {
  237. t.Fatalf(`expect: window "24h offset 14h" not to contain 12 hours ago; actual: %s contains %s`, dur24hOff14h, ago12h)
  238. }
  239. if !dur24hOff14h.Contains(ago36h) {
  240. t.Fatalf(`expect: window "24h offset 14h" to contain 36 hours ago; actual: %s does not contain %s`, dur24hOff14h, ago36h)
  241. }
  242. april152020, _ := time.Parse(time.RFC3339, "2020-04-15T00:00:00Z")
  243. april102020, _ := time.Parse(time.RFC3339, "2020-04-10T00:00:00Z")
  244. april052020, _ := time.Parse(time.RFC3339, "2020-04-05T00:00:00Z")
  245. // "2020-04-08T00:00:00Z,2020-04-12T00:00:00Z" should have 96 hour duration and not produce an error
  246. april8to12, err := ParseWindowUTC("2020-04-08T00:00:00Z,2020-04-12T00:00:00Z")
  247. if err != nil {
  248. t.Fatalf(`unexpected error parsing "2020-04-08T00:00:00Z,2020-04-12T00:00:00Z": %s`, err)
  249. }
  250. if april8to12.Duration().Hours() != 96 {
  251. t.Fatalf(`expect: window %s to have duration 96 hour; actual: %f hours`, april8to12, april8to12.Duration().Hours())
  252. }
  253. if !april8to12.Contains(april102020) {
  254. t.Fatalf(`expect: window April 8-12 to contain April 10; actual: %s doesn't contain %s`, april8to12, april102020)
  255. }
  256. if april8to12.Contains(april052020) {
  257. t.Fatalf(`expect: window April 8-12 to not contain April 5; actual: %s contains %s`, april8to12, april052020)
  258. }
  259. if april8to12.Contains(april152020) {
  260. t.Fatalf(`expect: window April 8-12 to not contain April 15; actual: %s contains %s`, april8to12, april152020)
  261. }
  262. march152020, _ := time.Parse(time.RFC3339, "2020-03-15T00:00:00Z")
  263. march102020, _ := time.Parse(time.RFC3339, "2020-03-10T00:00:00Z")
  264. march052020, _ := time.Parse(time.RFC3339, "2020-03-05T00:00:00Z")
  265. // "1583712000,1583884800" should have 48 hour duration and not produce an error
  266. march9to11, err := ParseWindowUTC("1583712000,1583884800")
  267. if err != nil {
  268. t.Fatalf(`unexpected error parsing "2020-04-08T00:00:00Z,2020-04-12T00:00:00Z": %s`, err)
  269. }
  270. if march9to11.Duration().Hours() != 48 {
  271. t.Fatalf(`expect: window %s to have duration 48 hour; actual: %f hours`, march9to11, march9to11.Duration().Hours())
  272. }
  273. if !march9to11.Contains(march102020) {
  274. t.Fatalf(`expect: window March 9-11 to contain March 10; actual: %s doesn't contain %s`, march9to11, march102020)
  275. }
  276. if march9to11.Contains(march052020) {
  277. t.Fatalf(`expect: window March 9-11 to not contain March 5; actual: %s contains %s`, march9to11, march052020)
  278. }
  279. if march9to11.Contains(march152020) {
  280. t.Fatalf(`expect: window March 9-11 to not contain March 15; actual: %s contains %s`, march9to11, march152020)
  281. }
  282. }
  283. func TestParseWindowWithOffsetString(t *testing.T) {
  284. // ParseWindowWithOffsetString should equal ParseWindowUTC when location == "UTC"
  285. // for all window string formats
  286. todayUTC, err := ParseWindowUTC("today")
  287. if err != nil {
  288. t.Fatalf(`unexpected error parsing "today": %s`, err)
  289. }
  290. todayTZ, err := ParseWindowWithOffsetString("today", "UTC")
  291. if err != nil {
  292. t.Fatalf(`unexpected error parsing "today": %s`, err)
  293. }
  294. if !todayUTC.ApproximatelyEqual(todayTZ, time.Millisecond) {
  295. t.Fatalf(`expect: window "today" UTC to equal "today" with timezone "UTC"; actual: %s not equal %s`, todayUTC, todayTZ)
  296. }
  297. yesterdayUTC, err := ParseWindowUTC("yesterday")
  298. if err != nil {
  299. t.Fatalf(`unexpected error parsing "yesterday": %s`, err)
  300. }
  301. yesterdayTZ, err := ParseWindowWithOffsetString("yesterday", "UTC")
  302. if err != nil {
  303. t.Fatalf(`unexpected error parsing "yesterday": %s`, err)
  304. }
  305. if !yesterdayUTC.ApproximatelyEqual(yesterdayTZ, time.Millisecond) {
  306. t.Fatalf(`expect: window "yesterday" UTC to equal "yesterday" with timezone "UTC"; actual: %s not equal %s`, yesterdayUTC, yesterdayTZ)
  307. }
  308. weekUTC, err := ParseWindowUTC("week")
  309. if err != nil {
  310. t.Fatalf(`unexpected error parsing "week": %s`, err)
  311. }
  312. weekTZ, err := ParseWindowWithOffsetString("week", "UTC")
  313. if err != nil {
  314. t.Fatalf(`unexpected error parsing "week": %s`, err)
  315. }
  316. if !weekUTC.ApproximatelyEqual(weekTZ, time.Millisecond) {
  317. t.Fatalf(`expect: window "week" UTC to equal "week" with timezone "UTC"; actual: %s not equal %s`, weekUTC, weekTZ)
  318. }
  319. monthUTC, err := ParseWindowUTC("month")
  320. if err != nil {
  321. t.Fatalf(`unexpected error parsing "month": %s`, err)
  322. }
  323. monthTZ, err := ParseWindowWithOffsetString("month", "UTC")
  324. if err != nil {
  325. t.Fatalf(`unexpected error parsing "month": %s`, err)
  326. }
  327. if !monthUTC.ApproximatelyEqual(monthTZ, time.Millisecond) {
  328. t.Fatalf(`expect: window "month" UTC to equal "month" with timezone "UTC"; actual: %s not equal %s`, monthUTC, monthTZ)
  329. }
  330. lastweekUTC, err := ParseWindowUTC("lastweek")
  331. if err != nil {
  332. t.Fatalf(`unexpected error parsing "lastweek": %s`, err)
  333. }
  334. lastweekTZ, err := ParseWindowWithOffsetString("lastweek", "UTC")
  335. if err != nil {
  336. t.Fatalf(`unexpected error parsing "lastweek": %s`, err)
  337. }
  338. if !lastweekUTC.ApproximatelyEqual(lastweekTZ, time.Millisecond) {
  339. t.Fatalf(`expect: window "lastweek" UTC to equal "lastweek" with timezone "UTC"; actual: %s not equal %s`, lastweekUTC, lastweekTZ)
  340. }
  341. lastmonthUTC, err := ParseWindowUTC("lastmonth")
  342. if err != nil {
  343. t.Fatalf(`unexpected error parsing "lastmonth": %s`, err)
  344. }
  345. lastmonthTZ, err := ParseWindowWithOffsetString("lastmonth", "UTC")
  346. if err != nil {
  347. t.Fatalf(`unexpected error parsing "lastmonth": %s`, err)
  348. }
  349. if !lastmonthUTC.ApproximatelyEqual(lastmonthTZ, time.Millisecond) {
  350. t.Fatalf(`expect: window "lastmonth" UTC to equal "lastmonth" with timezone "UTC"; actual: %s not equal %s`, lastmonthUTC, lastmonthTZ)
  351. }
  352. dur10mUTC, err := ParseWindowUTC("10m")
  353. if err != nil {
  354. t.Fatalf(`unexpected error parsing "10m": %s`, err)
  355. }
  356. dur10mTZ, err := ParseWindowWithOffsetString("10m", "UTC")
  357. if err != nil {
  358. t.Fatalf(`unexpected error parsing "10m": %s`, err)
  359. }
  360. if !dur10mUTC.ApproximatelyEqual(dur10mTZ, time.Millisecond) {
  361. t.Fatalf(`expect: window "10m" UTC to equal "10m" with timezone "UTC"; actual: %s not equal %s`, dur10mUTC, dur10mTZ)
  362. }
  363. dur24hUTC, err := ParseWindowUTC("24h")
  364. if err != nil {
  365. t.Fatalf(`unexpected error parsing "24h": %s`, err)
  366. }
  367. dur24hTZ, err := ParseWindowWithOffsetString("24h", "UTC")
  368. if err != nil {
  369. t.Fatalf(`unexpected error parsing "24h": %s`, err)
  370. }
  371. if !dur24hUTC.ApproximatelyEqual(dur24hTZ, time.Millisecond) {
  372. t.Fatalf(`expect: window "24h" UTC to equal "24h" with timezone "UTC"; actual: %s not equal %s`, dur24hUTC, dur24hTZ)
  373. }
  374. dur37dUTC, err := ParseWindowUTC("37d")
  375. if err != nil {
  376. t.Fatalf(`unexpected error parsing "37d": %s`, err)
  377. }
  378. dur37dTZ, err := ParseWindowWithOffsetString("37d", "UTC")
  379. if err != nil {
  380. t.Fatalf(`unexpected error parsing "37d": %s`, err)
  381. }
  382. if !dur37dUTC.ApproximatelyEqual(dur37dTZ, time.Millisecond) {
  383. t.Fatalf(`expect: window "37d" UTC to equal "37d" with timezone "UTC"; actual: %s not equal %s`, dur37dUTC, dur37dTZ)
  384. }
  385. // ParseWindowWithOffsetString should be the correct relative to ParseWindowUTC; i.e.
  386. // - for durations, the times should match, but the representations should differ
  387. // by the number of hours offset
  388. // - for words like "today" and "yesterday", the times may not match, in which
  389. // case, for example, "today" UTC-08:00 might equal "yesterday" UTC
  390. // fmtWindow only compares date and time to the minute, not second or
  391. // timezone. Helper for comparing timezone shifted windows.
  392. fmtWindow := func(w Window) string {
  393. s := "nil"
  394. if w.start != nil {
  395. s = w.start.Format("2006-01-02T15:04")
  396. }
  397. e := "nil"
  398. if w.end != nil {
  399. e = w.end.Format("2006-01-02T15:04")
  400. }
  401. return fmt.Sprintf("[%s, %s]", s, e)
  402. }
  403. // Test UTC-08:00 (California), UTC+03:00 (Moscow), UTC+12:00 (New Zealand), and UTC itself
  404. for _, offsetHrs := range []int{-8, 3, 12, 0} {
  405. offStr := fmt.Sprintf("+%02d:00", offsetHrs)
  406. if offsetHrs < 0 {
  407. offStr = fmt.Sprintf("-%02d:00", -offsetHrs)
  408. }
  409. off := time.Duration(offsetHrs) * time.Hour
  410. dur10mTZ, err = ParseWindowWithOffsetString("10m", offStr)
  411. if err != nil {
  412. t.Fatalf(`unexpected error parsing "10m": %s`, err)
  413. }
  414. if !dur10mTZ.ApproximatelyEqual(dur10mUTC, time.Second) {
  415. t.Fatalf(`expect: window "10m" UTC to equal "10m" with timezone "%s"; actual: %s not equal %s`, offStr, dur10mUTC, dur10mTZ)
  416. }
  417. if fmtWindow(dur10mTZ.Shift(-off)) != fmtWindow(dur10mUTC) {
  418. t.Fatalf(`expect: date, hour, and minute of window "10m" UTC to equal that of "10m" %s shifted by %s; actual: %s not equal %s`, offStr, off, fmtWindow(dur10mUTC), fmtWindow(dur10mTZ.Shift(-off)))
  419. }
  420. dur24hTZ, err = ParseWindowWithOffsetString("24h", offStr)
  421. if err != nil {
  422. t.Fatalf(`unexpected error parsing "24h": %s`, err)
  423. }
  424. if !dur24hTZ.ApproximatelyEqual(dur24hUTC, time.Second) {
  425. t.Fatalf(`expect: window "24h" UTC to equal "24h" with timezone "%s"; actual: %s not equal %s`, offStr, dur24hUTC, dur24hTZ)
  426. }
  427. if fmtWindow(dur24hTZ.Shift(-off)) != fmtWindow(dur24hUTC) {
  428. t.Fatalf(`expect: date, hour, and minute of window "24h" UTC to equal that of "24h" %s shifted by %s; actual: %s not equal %s`, offStr, off, fmtWindow(dur24hUTC), fmtWindow(dur24hTZ.Shift(-off)))
  429. }
  430. dur37dTZ, err = ParseWindowWithOffsetString("37d", offStr)
  431. if err != nil {
  432. t.Fatalf(`unexpected error parsing "37d": %s`, err)
  433. }
  434. if !dur37dTZ.ApproximatelyEqual(dur37dUTC, time.Second) {
  435. t.Fatalf(`expect: window "37d" UTC to equal "37d" with timezone "%s"; actual: %s not equal %s`, offStr, dur37dUTC, dur37dTZ)
  436. }
  437. if fmtWindow(dur37dTZ.Shift(-off)) != fmtWindow(dur37dUTC) {
  438. t.Fatalf(`expect: date, hour, and minute of window "37d" UTC to equal that of "37d" %s shifted by %s; actual: %s not equal %s`, offStr, off, fmtWindow(dur37dUTC), fmtWindow(dur37dTZ.Shift(-off)))
  439. }
  440. // "today" and "yesterday" should comply with the current day in each
  441. // respective timezone, depending on if it is ahead of, equal to, or
  442. // behind UTC at the given moment.
  443. todayTZ, err = ParseWindowWithOffsetString("today", offStr)
  444. if err != nil {
  445. t.Fatalf(`unexpected error parsing "today": %s`, err)
  446. }
  447. yesterdayTZ, err = ParseWindowWithOffsetString("yesterday", offStr)
  448. if err != nil {
  449. t.Fatalf(`unexpected error parsing "yesterday": %s`, err)
  450. }
  451. hoursSinceYesterdayUTC := time.Now().UTC().Sub(time.Now().UTC().Truncate(24.0 * time.Hour)).Hours()
  452. hoursUntilTomorrowUTC := 24.0 - hoursSinceYesterdayUTC
  453. aheadOfUTC := float64(offsetHrs)-hoursUntilTomorrowUTC > 0
  454. behindUTC := float64(offsetHrs)+hoursSinceYesterdayUTC < 0
  455. // yesterday in this timezone should equal today UTC
  456. if aheadOfUTC {
  457. if fmtWindow(yesterdayTZ) != fmtWindow(todayUTC) {
  458. t.Fatalf(`expect: window "today" UTC to equal "yesterday" with timezone "%s"; actual: %s not equal %s`, offStr, yesterdayTZ, todayUTC)
  459. }
  460. }
  461. // today in this timezone should equal yesterday UTC
  462. if behindUTC {
  463. if fmtWindow(todayTZ) != fmtWindow(yesterdayUTC) {
  464. t.Fatalf(`expect: window "today" UTC to equal "yesterday" with timezone "%s"; actual: %s not equal %s`, offStr, todayTZ, yesterdayUTC)
  465. }
  466. }
  467. // today in this timezone should equal today UTC, likewise for yesterday
  468. if !aheadOfUTC && !behindUTC {
  469. if fmtWindow(todayTZ) != fmtWindow(todayUTC) {
  470. t.Fatalf(`expect: window "today" UTC to equal "today" with timezone "%s"; actual: %s not equal %s`, offStr, todayTZ, todayUTC)
  471. }
  472. // yesterday in this timezone should equal yesterday UTC
  473. if fmtWindow(yesterdayTZ) != fmtWindow(yesterdayUTC) {
  474. t.Fatalf(`expect: window "yesterday" UTC to equal "yesterday" with timezone "%s"; actual: %s not equal %s`, offStr, yesterdayTZ, yesterdayUTC)
  475. }
  476. }
  477. }
  478. }
  479. func TestWindow_DurationOffsetStrings(t *testing.T) {
  480. w, err := ParseWindowUTC("1d")
  481. if err != nil {
  482. t.Fatalf(`unexpected error parsing "1d": %s`, err)
  483. }
  484. dur, off := w.DurationOffsetStrings()
  485. if dur != "1d" {
  486. t.Fatalf(`expect: window to be "1d"; actual: "%s"`, dur)
  487. }
  488. if off != "" {
  489. t.Fatalf(`expect: offset to be ""; actual: "%s"`, off)
  490. }
  491. w, err = ParseWindowUTC("3h")
  492. if err != nil {
  493. t.Fatalf(`unexpected error parsing "1d": %s`, err)
  494. }
  495. dur, off = w.DurationOffsetStrings()
  496. if dur != "3h" {
  497. t.Fatalf(`expect: window to be "3h"; actual: "%s"`, dur)
  498. }
  499. if off != "" {
  500. t.Fatalf(`expect: offset to be ""; actual: "%s"`, off)
  501. }
  502. w, err = ParseWindowUTC("10m")
  503. if err != nil {
  504. t.Fatalf(`unexpected error parsing "1d": %s`, err)
  505. }
  506. dur, off = w.DurationOffsetStrings()
  507. if dur != "10m" {
  508. t.Fatalf(`expect: window to be "10m"; actual: "%s"`, dur)
  509. }
  510. if off != "" {
  511. t.Fatalf(`expect: offset to be ""; actual: "%s"`, off)
  512. }
  513. w, err = ParseWindowUTC("1589448338,1589534798")
  514. if err != nil {
  515. t.Fatalf(`unexpected error parsing "1589448338,1589534798": %s`, err)
  516. }
  517. dur, off = w.DurationOffsetStrings()
  518. if dur != "1441m" {
  519. t.Fatalf(`expect: window to be "1441m"; actual: "%s"`, dur)
  520. }
  521. if off == "" {
  522. t.Fatalf(`expect: offset to not be ""; actual: "%s"`, off)
  523. }
  524. w, err = ParseWindowUTC("yesterday")
  525. if err != nil {
  526. t.Fatalf(`unexpected error parsing "1589448338,1589534798": %s`, err)
  527. }
  528. dur, _ = w.DurationOffsetStrings()
  529. if dur != "1d" {
  530. t.Fatalf(`expect: window to be "1d"; actual: "%s"`, dur)
  531. }
  532. }
  533. func TestWindow_DurationOffsetForPrometheus(t *testing.T) {
  534. // Set-up and tear-down
  535. thanosEnabled := env.GetBool(env.ThanosEnabledEnvVar, false)
  536. defer env.SetBool(env.ThanosEnabledEnvVar, thanosEnabled)
  537. // Test for Prometheus (env.IsThanosEnabled() == false)
  538. env.SetBool(env.ThanosEnabledEnvVar, false)
  539. if env.IsThanosEnabled() {
  540. t.Fatalf("expected env.IsThanosEnabled() == false")
  541. }
  542. w, err := ParseWindowUTC("1d")
  543. if err != nil {
  544. t.Fatalf(`unexpected error parsing "1d": %s`, err)
  545. }
  546. dur, off, err := w.DurationOffsetForPrometheus()
  547. if err != nil {
  548. t.Fatalf("unexpected error: %s", err)
  549. }
  550. if dur != "1d" {
  551. t.Fatalf(`expect: window to be "1d"; actual: "%s"`, dur)
  552. }
  553. if off != "" {
  554. t.Fatalf(`expect: offset to be ""; actual: "%s"`, off)
  555. }
  556. w, err = ParseWindowUTC("2h")
  557. if err != nil {
  558. t.Fatalf(`unexpected error parsing "2h": %s`, err)
  559. }
  560. dur, off, err = w.DurationOffsetForPrometheus()
  561. if err != nil {
  562. t.Fatalf("unexpected error: %s", err)
  563. }
  564. if dur != "2h" {
  565. t.Fatalf(`expect: window to be "2h"; actual: "%s"`, dur)
  566. }
  567. if off != "" {
  568. t.Fatalf(`expect: offset to be ""; actual: "%s"`, off)
  569. }
  570. w, err = ParseWindowUTC("10m")
  571. if err != nil {
  572. t.Fatalf(`unexpected error parsing "10m": %s`, err)
  573. }
  574. dur, off, err = w.DurationOffsetForPrometheus()
  575. if err != nil {
  576. t.Fatalf("unexpected error: %s", err)
  577. }
  578. if dur != "10m" {
  579. t.Fatalf(`expect: window to be "10m"; actual: "%s"`, dur)
  580. }
  581. if off != "" {
  582. t.Fatalf(`expect: offset to be ""; actual: "%s"`, off)
  583. }
  584. w, err = ParseWindowUTC("1589448338,1589534798")
  585. if err != nil {
  586. t.Fatalf(`unexpected error parsing "1589448338,1589534798": %s`, err)
  587. }
  588. dur, off, err = w.DurationOffsetForPrometheus()
  589. if err != nil {
  590. t.Fatalf("unexpected error: %s", err)
  591. }
  592. if dur != "1441m" {
  593. t.Fatalf(`expect: window to be "1441m"; actual: "%s"`, dur)
  594. }
  595. if !strings.HasPrefix(off, " offset ") {
  596. t.Fatalf(`expect: offset to start with " offset "; actual: "%s"`, off)
  597. }
  598. w, err = ParseWindowUTC("yesterday")
  599. if err != nil {
  600. t.Fatalf(`unexpected error parsing "yesterday": %s`, err)
  601. }
  602. dur, off, err = w.DurationOffsetForPrometheus()
  603. if err != nil {
  604. t.Fatalf("unexpected error: %s", err)
  605. }
  606. if dur != "1d" {
  607. t.Fatalf(`expect: window to be "1d"; actual: "%s"`, dur)
  608. }
  609. if !strings.HasPrefix(off, " offset ") {
  610. t.Fatalf(`expect: offset to start with " offset "; actual: "%s"`, off)
  611. }
  612. // Test for Thanos (env.IsThanosEnabled() == true)
  613. env.SetBool(env.ThanosEnabledEnvVar, true)
  614. if !env.IsThanosEnabled() {
  615. t.Fatalf("expected env.IsThanosEnabled() == true")
  616. }
  617. w, err = ParseWindowUTC("1d")
  618. if err != nil {
  619. t.Fatalf(`unexpected error parsing "1d": %s`, err)
  620. }
  621. dur, off, err = w.DurationOffsetForPrometheus()
  622. if err != nil {
  623. t.Fatalf("unexpected error: %s", err)
  624. }
  625. if dur != "21h" {
  626. t.Fatalf(`expect: window to be "21d"; actual: "%s"`, dur)
  627. }
  628. if off != " offset 3h" {
  629. t.Fatalf(`expect: offset to be " offset 3h"; actual: "%s"`, off)
  630. }
  631. w, err = ParseWindowUTC("2h")
  632. if err != nil {
  633. t.Fatalf(`unexpected error parsing "2h": %s`, err)
  634. }
  635. dur, off, err = w.DurationOffsetForPrometheus()
  636. if err == nil {
  637. t.Fatalf(`expected error (negative duration); got ("%s", "%s")`, dur, off)
  638. }
  639. w, err = ParseWindowUTC("10m")
  640. if err != nil {
  641. t.Fatalf(`unexpected error parsing "1d": %s`, err)
  642. }
  643. dur, off, err = w.DurationOffsetForPrometheus()
  644. if err == nil {
  645. t.Fatalf(`expected error (negative duration); got ("%s", "%s")`, dur, off)
  646. }
  647. w, err = ParseWindowUTC("1589448338,1589534798")
  648. if err != nil {
  649. t.Fatalf(`unexpected error parsing "1589448338,1589534798": %s`, err)
  650. }
  651. dur, off, err = w.DurationOffsetForPrometheus()
  652. if err != nil {
  653. t.Fatalf("unexpected error: %s", err)
  654. }
  655. if dur != "1441m" {
  656. t.Fatalf(`expect: window to be "1441m"; actual: "%s"`, dur)
  657. }
  658. if !strings.HasPrefix(off, " offset ") {
  659. t.Fatalf(`expect: offset to start with " offset "; actual: "%s"`, off)
  660. }
  661. }
  662. // TODO
  663. // func TestWindow_Overlaps(t *testing.T) {}
  664. // TODO
  665. // func TestWindow_Contains(t *testing.T) {}
  666. // TODO
  667. // func TestWindow_Duration(t *testing.T) {}
  668. // TODO
  669. // func TestWindow_End(t *testing.T) {}
  670. // TODO
  671. // func TestWindow_Equal(t *testing.T) {}
  672. // TODO
  673. // func TestWindow_ExpandStart(t *testing.T) {}
  674. // TODO
  675. // func TestWindow_ExpandEnd(t *testing.T) {}
  676. func TestWindow_Expand(t *testing.T) {
  677. t1 := time.Now().Round(time.Hour)
  678. t2 := t1.Add(34 * time.Minute)
  679. t3 := t1.Add(50 * time.Minute)
  680. t4 := t1.Add(84 * time.Minute)
  681. cases := []struct {
  682. windowToExpand Window
  683. windowArgument Window
  684. expected Window
  685. }{
  686. {
  687. windowToExpand: NewClosedWindow(t1, t2),
  688. windowArgument: NewClosedWindow(t3, t4),
  689. expected: NewClosedWindow(t1, t4),
  690. },
  691. {
  692. windowToExpand: NewClosedWindow(t3, t4),
  693. windowArgument: NewClosedWindow(t1, t2),
  694. expected: NewClosedWindow(t1, t4),
  695. },
  696. {
  697. windowToExpand: NewClosedWindow(t1, t3),
  698. windowArgument: NewClosedWindow(t2, t4),
  699. expected: NewClosedWindow(t1, t4),
  700. },
  701. {
  702. windowToExpand: NewClosedWindow(t2, t4),
  703. windowArgument: NewClosedWindow(t1, t3),
  704. expected: NewClosedWindow(t1, t4),
  705. },
  706. {
  707. windowToExpand: Window{},
  708. windowArgument: NewClosedWindow(t1, t2),
  709. expected: NewClosedWindow(t1, t2),
  710. },
  711. {
  712. windowToExpand: NewWindow(nil, &t2),
  713. windowArgument: NewWindow(nil, &t3),
  714. expected: NewWindow(nil, &t3),
  715. },
  716. {
  717. windowToExpand: NewWindow(&t2, nil),
  718. windowArgument: NewWindow(&t1, nil),
  719. expected: NewWindow(&t1, nil),
  720. },
  721. }
  722. for _, c := range cases {
  723. result := c.windowToExpand.Expand(c.windowArgument)
  724. if !result.Equal(c.expected) {
  725. t.Errorf("Expand %s with %s, expected %s but got %s", c.windowToExpand, c.windowArgument, c.expected, result)
  726. }
  727. }
  728. }
  729. // TODO
  730. // func TestWindow_Start(t *testing.T) {}
  731. // TODO
  732. // func TestWindow_String(t *testing.T) {}