window_test.go 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847
  1. package kubecost
  2. import (
  3. "fmt"
  4. "strings"
  5. "testing"
  6. "time"
  7. "github.com/opencost/opencost/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 BenchmarkParseWindowUTC(b *testing.B) {
  284. for n := 0; n < b.N; n++ {
  285. _, err := ParseWindowUTC("2020-04-08T00:00:00Z,2020-04-12T00:00:00Z")
  286. if err != nil {
  287. b.Fatalf("error running benchmark: %s", err.Error())
  288. }
  289. }
  290. }
  291. func TestParseWindowWithOffsetString(t *testing.T) {
  292. // ParseWindowWithOffsetString should equal ParseWindowUTC when location == "UTC"
  293. // for all window string formats
  294. todayUTC, err := ParseWindowUTC("today")
  295. if err != nil {
  296. t.Fatalf(`unexpected error parsing "today": %s`, err)
  297. }
  298. todayTZ, err := ParseWindowWithOffsetString("today", "UTC")
  299. if err != nil {
  300. t.Fatalf(`unexpected error parsing "today": %s`, err)
  301. }
  302. if !todayUTC.ApproximatelyEqual(todayTZ, time.Millisecond) {
  303. t.Fatalf(`expect: window "today" UTC to equal "today" with timezone "UTC"; actual: %s not equal %s`, todayUTC, todayTZ)
  304. }
  305. yesterdayUTC, err := ParseWindowUTC("yesterday")
  306. if err != nil {
  307. t.Fatalf(`unexpected error parsing "yesterday": %s`, err)
  308. }
  309. yesterdayTZ, err := ParseWindowWithOffsetString("yesterday", "UTC")
  310. if err != nil {
  311. t.Fatalf(`unexpected error parsing "yesterday": %s`, err)
  312. }
  313. if !yesterdayUTC.ApproximatelyEqual(yesterdayTZ, time.Millisecond) {
  314. t.Fatalf(`expect: window "yesterday" UTC to equal "yesterday" with timezone "UTC"; actual: %s not equal %s`, yesterdayUTC, yesterdayTZ)
  315. }
  316. weekUTC, err := ParseWindowUTC("week")
  317. if err != nil {
  318. t.Fatalf(`unexpected error parsing "week": %s`, err)
  319. }
  320. weekTZ, err := ParseWindowWithOffsetString("week", "UTC")
  321. if err != nil {
  322. t.Fatalf(`unexpected error parsing "week": %s`, err)
  323. }
  324. if !weekUTC.ApproximatelyEqual(weekTZ, time.Millisecond) {
  325. t.Fatalf(`expect: window "week" UTC to equal "week" with timezone "UTC"; actual: %s not equal %s`, weekUTC, weekTZ)
  326. }
  327. monthUTC, err := ParseWindowUTC("month")
  328. if err != nil {
  329. t.Fatalf(`unexpected error parsing "month": %s`, err)
  330. }
  331. monthTZ, err := ParseWindowWithOffsetString("month", "UTC")
  332. if err != nil {
  333. t.Fatalf(`unexpected error parsing "month": %s`, err)
  334. }
  335. if !monthUTC.ApproximatelyEqual(monthTZ, time.Millisecond) {
  336. t.Fatalf(`expect: window "month" UTC to equal "month" with timezone "UTC"; actual: %s not equal %s`, monthUTC, monthTZ)
  337. }
  338. lastweekUTC, err := ParseWindowUTC("lastweek")
  339. if err != nil {
  340. t.Fatalf(`unexpected error parsing "lastweek": %s`, err)
  341. }
  342. lastweekTZ, err := ParseWindowWithOffsetString("lastweek", "UTC")
  343. if err != nil {
  344. t.Fatalf(`unexpected error parsing "lastweek": %s`, err)
  345. }
  346. if !lastweekUTC.ApproximatelyEqual(lastweekTZ, time.Millisecond) {
  347. t.Fatalf(`expect: window "lastweek" UTC to equal "lastweek" with timezone "UTC"; actual: %s not equal %s`, lastweekUTC, lastweekTZ)
  348. }
  349. lastmonthUTC, err := ParseWindowUTC("lastmonth")
  350. if err != nil {
  351. t.Fatalf(`unexpected error parsing "lastmonth": %s`, err)
  352. }
  353. lastmonthTZ, err := ParseWindowWithOffsetString("lastmonth", "UTC")
  354. if err != nil {
  355. t.Fatalf(`unexpected error parsing "lastmonth": %s`, err)
  356. }
  357. if !lastmonthUTC.ApproximatelyEqual(lastmonthTZ, time.Millisecond) {
  358. t.Fatalf(`expect: window "lastmonth" UTC to equal "lastmonth" with timezone "UTC"; actual: %s not equal %s`, lastmonthUTC, lastmonthTZ)
  359. }
  360. dur10mUTC, err := ParseWindowUTC("10m")
  361. if err != nil {
  362. t.Fatalf(`unexpected error parsing "10m": %s`, err)
  363. }
  364. dur10mTZ, err := ParseWindowWithOffsetString("10m", "UTC")
  365. if err != nil {
  366. t.Fatalf(`unexpected error parsing "10m": %s`, err)
  367. }
  368. if !dur10mUTC.ApproximatelyEqual(dur10mTZ, time.Millisecond) {
  369. t.Fatalf(`expect: window "10m" UTC to equal "10m" with timezone "UTC"; actual: %s not equal %s`, dur10mUTC, dur10mTZ)
  370. }
  371. dur24hUTC, err := ParseWindowUTC("24h")
  372. if err != nil {
  373. t.Fatalf(`unexpected error parsing "24h": %s`, err)
  374. }
  375. dur24hTZ, err := ParseWindowWithOffsetString("24h", "UTC")
  376. if err != nil {
  377. t.Fatalf(`unexpected error parsing "24h": %s`, err)
  378. }
  379. if !dur24hUTC.ApproximatelyEqual(dur24hTZ, time.Millisecond) {
  380. t.Fatalf(`expect: window "24h" UTC to equal "24h" with timezone "UTC"; actual: %s not equal %s`, dur24hUTC, dur24hTZ)
  381. }
  382. dur37dUTC, err := ParseWindowUTC("37d")
  383. if err != nil {
  384. t.Fatalf(`unexpected error parsing "37d": %s`, err)
  385. }
  386. dur37dTZ, err := ParseWindowWithOffsetString("37d", "UTC")
  387. if err != nil {
  388. t.Fatalf(`unexpected error parsing "37d": %s`, err)
  389. }
  390. if !dur37dUTC.ApproximatelyEqual(dur37dTZ, time.Millisecond) {
  391. t.Fatalf(`expect: window "37d" UTC to equal "37d" with timezone "UTC"; actual: %s not equal %s`, dur37dUTC, dur37dTZ)
  392. }
  393. // ParseWindowWithOffsetString should be the correct relative to ParseWindowUTC; i.e.
  394. // - for durations, the times should match, but the representations should differ
  395. // by the number of hours offset
  396. // - for words like "today" and "yesterday", the times may not match, in which
  397. // case, for example, "today" UTC-08:00 might equal "yesterday" UTC
  398. // fmtWindow only compares date and time to the minute, not second or
  399. // timezone. Helper for comparing timezone shifted windows.
  400. fmtWindow := func(w Window) string {
  401. s := "nil"
  402. if w.start != nil {
  403. s = w.start.Format("2006-01-02T15:04")
  404. }
  405. e := "nil"
  406. if w.end != nil {
  407. e = w.end.Format("2006-01-02T15:04")
  408. }
  409. return fmt.Sprintf("[%s, %s]", s, e)
  410. }
  411. // Test UTC-08:00 (California), UTC+03:00 (Moscow), UTC+12:00 (New Zealand), and UTC itself
  412. for _, offsetHrs := range []int{-8, 3, 12, 0} {
  413. offStr := fmt.Sprintf("+%02d:00", offsetHrs)
  414. if offsetHrs < 0 {
  415. offStr = fmt.Sprintf("-%02d:00", -offsetHrs)
  416. }
  417. off := time.Duration(offsetHrs) * time.Hour
  418. dur10mTZ, err = ParseWindowWithOffsetString("10m", offStr)
  419. if err != nil {
  420. t.Fatalf(`unexpected error parsing "10m": %s`, err)
  421. }
  422. if !dur10mTZ.ApproximatelyEqual(dur10mUTC, time.Second) {
  423. t.Fatalf(`expect: window "10m" UTC to equal "10m" with timezone "%s"; actual: %s not equal %s`, offStr, dur10mUTC, dur10mTZ)
  424. }
  425. if fmtWindow(dur10mTZ.Shift(-off)) != fmtWindow(dur10mUTC) {
  426. 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)))
  427. }
  428. dur24hTZ, err = ParseWindowWithOffsetString("24h", offStr)
  429. if err != nil {
  430. t.Fatalf(`unexpected error parsing "24h": %s`, err)
  431. }
  432. if !dur24hTZ.ApproximatelyEqual(dur24hUTC, time.Second) {
  433. t.Fatalf(`expect: window "24h" UTC to equal "24h" with timezone "%s"; actual: %s not equal %s`, offStr, dur24hUTC, dur24hTZ)
  434. }
  435. if fmtWindow(dur24hTZ.Shift(-off)) != fmtWindow(dur24hUTC) {
  436. 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)))
  437. }
  438. dur37dTZ, err = ParseWindowWithOffsetString("37d", offStr)
  439. if err != nil {
  440. t.Fatalf(`unexpected error parsing "37d": %s`, err)
  441. }
  442. if !dur37dTZ.ApproximatelyEqual(dur37dUTC, time.Second) {
  443. t.Fatalf(`expect: window "37d" UTC to equal "37d" with timezone "%s"; actual: %s not equal %s`, offStr, dur37dUTC, dur37dTZ)
  444. }
  445. if fmtWindow(dur37dTZ.Shift(-off)) != fmtWindow(dur37dUTC) {
  446. 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)))
  447. }
  448. // "today" and "yesterday" should comply with the current day in each
  449. // respective timezone, depending on if it is ahead of, equal to, or
  450. // behind UTC at the given moment.
  451. todayTZ, err = ParseWindowWithOffsetString("today", offStr)
  452. if err != nil {
  453. t.Fatalf(`unexpected error parsing "today": %s`, err)
  454. }
  455. yesterdayTZ, err = ParseWindowWithOffsetString("yesterday", offStr)
  456. if err != nil {
  457. t.Fatalf(`unexpected error parsing "yesterday": %s`, err)
  458. }
  459. hoursSinceYesterdayUTC := time.Now().UTC().Sub(time.Now().UTC().Truncate(24.0 * time.Hour)).Hours()
  460. hoursUntilTomorrowUTC := 24.0 - hoursSinceYesterdayUTC
  461. aheadOfUTC := float64(offsetHrs)-hoursUntilTomorrowUTC > 0
  462. behindUTC := float64(offsetHrs)+hoursSinceYesterdayUTC < 0
  463. // yesterday in this timezone should equal today UTC
  464. if aheadOfUTC {
  465. if fmtWindow(yesterdayTZ) != fmtWindow(todayUTC) {
  466. t.Fatalf(`expect: window "today" UTC to equal "yesterday" with timezone "%s"; actual: %s not equal %s`, offStr, yesterdayTZ, todayUTC)
  467. }
  468. }
  469. // today in this timezone should equal yesterday UTC
  470. if behindUTC {
  471. if fmtWindow(todayTZ) != fmtWindow(yesterdayUTC) {
  472. t.Fatalf(`expect: window "today" UTC to equal "yesterday" with timezone "%s"; actual: %s not equal %s`, offStr, todayTZ, yesterdayUTC)
  473. }
  474. }
  475. // today in this timezone should equal today UTC, likewise for yesterday
  476. if !aheadOfUTC && !behindUTC {
  477. if fmtWindow(todayTZ) != fmtWindow(todayUTC) {
  478. t.Fatalf(`expect: window "today" UTC to equal "today" with timezone "%s"; actual: %s not equal %s`, offStr, todayTZ, todayUTC)
  479. }
  480. // yesterday in this timezone should equal yesterday UTC
  481. if fmtWindow(yesterdayTZ) != fmtWindow(yesterdayUTC) {
  482. t.Fatalf(`expect: window "yesterday" UTC to equal "yesterday" with timezone "%s"; actual: %s not equal %s`, offStr, yesterdayTZ, yesterdayUTC)
  483. }
  484. }
  485. }
  486. }
  487. func TestWindow_DurationOffsetStrings(t *testing.T) {
  488. w, err := ParseWindowUTC("1d")
  489. if err != nil {
  490. t.Fatalf(`unexpected error parsing "1d": %s`, err)
  491. }
  492. dur, off := w.DurationOffsetStrings()
  493. if dur != "1d" {
  494. t.Fatalf(`expect: window to be "1d"; actual: "%s"`, dur)
  495. }
  496. if off != "" {
  497. t.Fatalf(`expect: offset to be ""; actual: "%s"`, off)
  498. }
  499. w, err = ParseWindowUTC("3h")
  500. if err != nil {
  501. t.Fatalf(`unexpected error parsing "1d": %s`, err)
  502. }
  503. dur, off = w.DurationOffsetStrings()
  504. if dur != "3h" {
  505. t.Fatalf(`expect: window to be "3h"; actual: "%s"`, dur)
  506. }
  507. if off != "" {
  508. t.Fatalf(`expect: offset to be ""; actual: "%s"`, off)
  509. }
  510. w, err = ParseWindowUTC("10m")
  511. if err != nil {
  512. t.Fatalf(`unexpected error parsing "1d": %s`, err)
  513. }
  514. dur, off = w.DurationOffsetStrings()
  515. if dur != "10m" {
  516. t.Fatalf(`expect: window to be "10m"; actual: "%s"`, dur)
  517. }
  518. if off != "" {
  519. t.Fatalf(`expect: offset to be ""; actual: "%s"`, off)
  520. }
  521. w, err = ParseWindowUTC("1589448338,1589534798")
  522. if err != nil {
  523. t.Fatalf(`unexpected error parsing "1589448338,1589534798": %s`, err)
  524. }
  525. dur, off = w.DurationOffsetStrings()
  526. if dur != "1441m" {
  527. t.Fatalf(`expect: window to be "1441m"; actual: "%s"`, dur)
  528. }
  529. if off == "" {
  530. t.Fatalf(`expect: offset to not be ""; actual: "%s"`, off)
  531. }
  532. w, err = ParseWindowUTC("yesterday")
  533. if err != nil {
  534. t.Fatalf(`unexpected error parsing "1589448338,1589534798": %s`, err)
  535. }
  536. dur, _ = w.DurationOffsetStrings()
  537. if dur != "1d" {
  538. t.Fatalf(`expect: window to be "1d"; actual: "%s"`, dur)
  539. }
  540. }
  541. func TestWindow_DurationOffsetForPrometheus(t *testing.T) {
  542. // Set-up and tear-down
  543. thanosEnabled := env.GetBool(env.ThanosEnabledEnvVar, false)
  544. defer env.SetBool(env.ThanosEnabledEnvVar, thanosEnabled)
  545. // Test for Prometheus (env.IsThanosEnabled() == false)
  546. env.SetBool(env.ThanosEnabledEnvVar, false)
  547. if env.IsThanosEnabled() {
  548. t.Fatalf("expected env.IsThanosEnabled() == false")
  549. }
  550. w, err := ParseWindowUTC("1d")
  551. if err != nil {
  552. t.Fatalf(`unexpected error parsing "1d": %s`, err)
  553. }
  554. dur, off, err := w.DurationOffsetForPrometheus()
  555. if err != nil {
  556. t.Fatalf("unexpected error: %s", err)
  557. }
  558. if dur != "1d" {
  559. t.Fatalf(`expect: window to be "1d"; actual: "%s"`, dur)
  560. }
  561. if off != "" {
  562. t.Fatalf(`expect: offset to be ""; actual: "%s"`, off)
  563. }
  564. w, err = ParseWindowUTC("2h")
  565. if err != nil {
  566. t.Fatalf(`unexpected error parsing "2h": %s`, err)
  567. }
  568. dur, off, err = w.DurationOffsetForPrometheus()
  569. if err != nil {
  570. t.Fatalf("unexpected error: %s", err)
  571. }
  572. if dur != "2h" {
  573. t.Fatalf(`expect: window to be "2h"; actual: "%s"`, dur)
  574. }
  575. if off != "" {
  576. t.Fatalf(`expect: offset to be ""; actual: "%s"`, off)
  577. }
  578. w, err = ParseWindowUTC("10m")
  579. if err != nil {
  580. t.Fatalf(`unexpected error parsing "10m": %s`, err)
  581. }
  582. dur, off, err = w.DurationOffsetForPrometheus()
  583. if err != nil {
  584. t.Fatalf("unexpected error: %s", err)
  585. }
  586. if dur != "10m" {
  587. t.Fatalf(`expect: window to be "10m"; actual: "%s"`, dur)
  588. }
  589. if off != "" {
  590. t.Fatalf(`expect: offset to be ""; actual: "%s"`, off)
  591. }
  592. w, err = ParseWindowUTC("1589448338,1589534798")
  593. if err != nil {
  594. t.Fatalf(`unexpected error parsing "1589448338,1589534798": %s`, err)
  595. }
  596. dur, off, err = w.DurationOffsetForPrometheus()
  597. if err != nil {
  598. t.Fatalf("unexpected error: %s", err)
  599. }
  600. if dur != "1441m" {
  601. t.Fatalf(`expect: window to be "1441m"; actual: "%s"`, dur)
  602. }
  603. if !strings.HasPrefix(off, " offset ") {
  604. t.Fatalf(`expect: offset to start with " offset "; actual: "%s"`, off)
  605. }
  606. w, err = ParseWindowUTC("yesterday")
  607. if err != nil {
  608. t.Fatalf(`unexpected error parsing "yesterday": %s`, err)
  609. }
  610. dur, off, err = w.DurationOffsetForPrometheus()
  611. if err != nil {
  612. t.Fatalf("unexpected error: %s", err)
  613. }
  614. if dur != "1d" {
  615. t.Fatalf(`expect: window to be "1d"; actual: "%s"`, dur)
  616. }
  617. if !strings.HasPrefix(off, " offset ") {
  618. t.Fatalf(`expect: offset to start with " offset "; actual: "%s"`, off)
  619. }
  620. // Test for Thanos (env.IsThanosEnabled() == true)
  621. env.SetBool(env.ThanosEnabledEnvVar, true)
  622. if !env.IsThanosEnabled() {
  623. t.Fatalf("expected env.IsThanosEnabled() == true")
  624. }
  625. w, err = ParseWindowUTC("1d")
  626. if err != nil {
  627. t.Fatalf(`unexpected error parsing "1d": %s`, err)
  628. }
  629. dur, off, err = w.DurationOffsetForPrometheus()
  630. if err != nil {
  631. t.Fatalf("unexpected error: %s", err)
  632. }
  633. if dur != "21h" {
  634. t.Fatalf(`expect: window to be "21d"; actual: "%s"`, dur)
  635. }
  636. if off != " offset 3h" {
  637. t.Fatalf(`expect: offset to be " offset 3h"; actual: "%s"`, off)
  638. }
  639. w, err = ParseWindowUTC("2h")
  640. if err != nil {
  641. t.Fatalf(`unexpected error parsing "2h": %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("10m")
  648. if err != nil {
  649. t.Fatalf(`unexpected error parsing "1d": %s`, err)
  650. }
  651. dur, off, err = w.DurationOffsetForPrometheus()
  652. if err == nil {
  653. t.Fatalf(`expected error (negative duration); got ("%s", "%s")`, dur, off)
  654. }
  655. w, err = ParseWindowUTC("1589448338,1589534798")
  656. if err != nil {
  657. t.Fatalf(`unexpected error parsing "1589448338,1589534798": %s`, err)
  658. }
  659. dur, off, err = w.DurationOffsetForPrometheus()
  660. if err != nil {
  661. t.Fatalf("unexpected error: %s", err)
  662. }
  663. if dur != "1441m" {
  664. t.Fatalf(`expect: window to be "1441m"; actual: "%s"`, dur)
  665. }
  666. if !strings.HasPrefix(off, " offset ") {
  667. t.Fatalf(`expect: offset to start with " offset "; actual: "%s"`, off)
  668. }
  669. }
  670. // TODO
  671. // func TestWindow_Overlaps(t *testing.T) {}
  672. // TODO
  673. // func TestWindow_Contains(t *testing.T) {}
  674. // TODO
  675. // func TestWindow_Duration(t *testing.T) {}
  676. // TODO
  677. // func TestWindow_End(t *testing.T) {}
  678. // TODO
  679. // func TestWindow_Equal(t *testing.T) {}
  680. // TODO
  681. // func TestWindow_ExpandStart(t *testing.T) {}
  682. // TODO
  683. // func TestWindow_ExpandEnd(t *testing.T) {}
  684. func TestWindow_Expand(t *testing.T) {
  685. t1 := time.Now().Round(time.Hour)
  686. t2 := t1.Add(34 * time.Minute)
  687. t3 := t1.Add(50 * time.Minute)
  688. t4 := t1.Add(84 * time.Minute)
  689. cases := []struct {
  690. windowToExpand Window
  691. windowArgument Window
  692. expected Window
  693. }{
  694. {
  695. windowToExpand: NewClosedWindow(t1, t2),
  696. windowArgument: NewClosedWindow(t3, t4),
  697. expected: NewClosedWindow(t1, t4),
  698. },
  699. {
  700. windowToExpand: NewClosedWindow(t3, t4),
  701. windowArgument: NewClosedWindow(t1, t2),
  702. expected: NewClosedWindow(t1, t4),
  703. },
  704. {
  705. windowToExpand: NewClosedWindow(t1, t3),
  706. windowArgument: NewClosedWindow(t2, t4),
  707. expected: NewClosedWindow(t1, t4),
  708. },
  709. {
  710. windowToExpand: NewClosedWindow(t2, t4),
  711. windowArgument: NewClosedWindow(t1, t3),
  712. expected: NewClosedWindow(t1, t4),
  713. },
  714. {
  715. windowToExpand: Window{},
  716. windowArgument: NewClosedWindow(t1, t2),
  717. expected: NewClosedWindow(t1, t2),
  718. },
  719. {
  720. windowToExpand: NewWindow(nil, &t2),
  721. windowArgument: NewWindow(nil, &t3),
  722. expected: NewWindow(nil, &t3),
  723. },
  724. {
  725. windowToExpand: NewWindow(&t2, nil),
  726. windowArgument: NewWindow(&t1, nil),
  727. expected: NewWindow(&t1, nil),
  728. },
  729. }
  730. for _, c := range cases {
  731. result := c.windowToExpand.Expand(c.windowArgument)
  732. if !result.Equal(c.expected) {
  733. t.Errorf("Expand %s with %s, expected %s but got %s", c.windowToExpand, c.windowArgument, c.expected, result)
  734. }
  735. }
  736. }
  737. // TODO
  738. // func TestWindow_Start(t *testing.T) {}
  739. // TODO
  740. // func TestWindow_String(t *testing.T) {}