crono 1.1.1 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (231) hide show
  1. checksums.yaml +5 -5
  2. data/LICENSE +2 -3
  3. data/Rakefile +3 -4
  4. data/app/assets/javascripts/crono/materialize.min.js +6 -0
  5. data/app/assets/stylesheets/crono/application.css +26 -0
  6. data/app/assets/stylesheets/crono/materialize.min.css +31 -0
  7. data/app/controllers/crono/application_controller.rb +5 -0
  8. data/app/controllers/crono/jobs_controller.rb +11 -0
  9. data/app/models/crono/application_record.rb +5 -0
  10. data/{lib/crono/orm/active_record → app/models/crono}/crono_job.rb +1 -2
  11. data/app/views/crono/jobs/index.html.erb +50 -0
  12. data/app/views/crono/jobs/show.html.erb +16 -0
  13. data/app/views/layouts/crono/application.html.erb +31 -0
  14. data/{exe → bin}/crono +0 -0
  15. data/config/routes.rb +4 -0
  16. data/lib/crono/cli.rb +4 -21
  17. data/lib/crono/config.rb +2 -3
  18. data/lib/crono/engine.rb +15 -0
  19. data/lib/crono/job.rb +11 -7
  20. data/lib/crono/performer_proxy.rb +6 -5
  21. data/lib/crono/period.rb +6 -5
  22. data/lib/crono/time_of_day.rb +2 -2
  23. data/lib/crono/version.rb +1 -1
  24. data/lib/crono.rb +3 -3
  25. data/lib/generators/crono/install/install_generator.rb +12 -1
  26. data/lib/generators/crono/install/templates/migrations/create_crono_jobs.rb +3 -7
  27. data/spec/assets/bad_cronotab.rb +12 -0
  28. data/spec/assets/good_cronotab.rb +9 -0
  29. data/spec/cli_spec.rb +110 -0
  30. data/spec/config_spec.rb +47 -0
  31. data/spec/crono_spec.rb +7 -0
  32. data/spec/cronotab_spec.rb +20 -0
  33. data/spec/internal/:memory +0 -0
  34. data/{log/.keep → spec/internal/app/assets/config/manifest.js} +0 -0
  35. data/spec/internal/app/controllers/application_controller.rb +3 -0
  36. data/spec/internal/app/controllers/pages_controller.rb +5 -0
  37. data/spec/internal/app/views/pages/index.html.erb +1 -0
  38. data/spec/internal/config/application.rb +22 -0
  39. data/spec/internal/config/boot.rb +5 -0
  40. data/spec/internal/config/database.yml +3 -0
  41. data/spec/internal/config/environment.rb +2 -0
  42. data/spec/internal/config/routes.rb +3 -0
  43. data/spec/internal/config/storage.yml +3 -0
  44. data/spec/internal/db/crono_test.sqlite +0 -0
  45. data/spec/internal/db/schema.rb +10 -0
  46. data/spec/internal/log/test.log +6285 -0
  47. data/{tmp/.gitkeep → spec/internal/public/favicon.ico} +0 -0
  48. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/-3/-39xcDaxqOfT5_YbKGMC2VoOLHUFg1gyuxeTL-Eup7k.cache +0 -0
  49. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/-5/-5qRtN26mFn5ud6yyw5E5MpFhT61YcDJPMTlAoDLkEs.cache +2 -0
  50. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/-C/-C4fFLLfC1HB9flvtZbgPTj9oNiDtq1bvp8LPbnRv1k.cache +0 -0
  51. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/01/01tpm-y3svQKpsPvOHFWMxWBaG_35EVrgmnB5HKppZw.cache +1 -0
  52. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/0K/0KEVKDTACcfyL6Fl5QZ6qSNOeswfd6FloUlWXQvwsHg.cache +0 -0
  53. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/1F/1FwwOzgRWJEUxIctLIsrhuh9z3QKl1XWDCcdfRhgKPk.cache +2 -0
  54. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/1I/1IjQIP_VOqgJXKhZpmU6BzJwwAQQ13eyxAePeBRHXEg.cache +2 -0
  55. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/1b/1bH_3dMMKuACfI0TmmCeyB8zoKIiuiZsSSP6HhRXLbA.cache +3 -0
  56. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/1n/1n5Wp1qC4BYhgEaXBnkRpO1N0oVAwLVdn47Ebk5nduY.cache +1 -0
  57. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/22/22NEh4OGcNmK5Qh9ss73s622T6309DH8zv5ABF6-6Uk.cache +0 -0
  58. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/2n/2nEu77IGZHZPjlliLtJofREUZgmt-DGEhQnGIDMd0vA.cache +1 -0
  59. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/2q/2qyBEUowJIKm_1EF_tbDWLjGDvVCbTjiHtRX_PEVn-U.cache +1 -0
  60. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/2s/2sJPeHyInPjaKqPfsEWbBdNYaCzFtvcr5nk8VlQjHvY.cache +0 -0
  61. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/3k/3khLNNSEoPg-2idoNmihX2Ba1loklzJsXvSRXujPaRk.cache +1 -0
  62. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/47/479UaX78ImQpq18DwAHbpTAKCQcaS_g9kEFwudRxxC8.cache +1 -0
  63. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/7A/7A_YhyGyNk4Me5J0-p-XwK0qelTclb_2WX5pq0eY9dY.cache +1 -0
  64. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/7H/7H6n_CYmS-913TL8a3U1alFRI5CWy18J8jnwdLL_Gro.cache +1 -0
  65. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/8b/8bJ6EwvIAjMXaNe9Ic4zq8rauZNb-xiW27gp1sQAY44.cache +1 -0
  66. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/8d/8daZARHc9PDr5TZ5fqfR-_vJ5kCRzsZQKhQxFkrg_XI.cache +0 -0
  67. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/90/90vzKM32bOX3CR_IP7i2_AHgZSmn82XN_9gLyqidtUc.cache +2 -0
  68. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/9V/9V7dOKixQfEwAEIweL7rD4CUSL51MqiMeB67I5xhwXs.cache +0 -0
  69. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/9a/9aYqlHyIyZYckdfIJ3GbxrCRrsRgbc0wfZBCfd1lVWE.cache +0 -0
  70. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/Ac/AcdbxQ2RjSGjrVJSdCjS-HN_RI3GZbW86dZGhSD8Ots.cache +0 -0
  71. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/B1/B1NZJ5pbmAf6UeZz0obPpXJm4h-n3_qQvYyzru-IRXM.cache +2 -0
  72. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/Bi/BicUSIwup1Ebl9N94HOoznAu2a8p4LqqIf0swCsboJk.cache +1 -0
  73. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/CT/CTH9PjxIhN-xu3gPlv5Xo1d1AFJOX0ETdUqvh74MZN4.cache +0 -0
  74. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/Cj/Cjzu7j1PyqHJD0alH5TiAYfJDN0Zbxhg4zFZegEzuYM.cache +0 -0
  75. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/DC/DC_euXbiKWPXx4qSkPGLNU4C2RIvjMBWliBrTgl6Mqo.cache +1 -0
  76. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/Db/DboywI_AoqjU7aQB5p90ZP76JW8sinmm4tMgjEG8Iu8.cache +1 -0
  77. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/Dz/DzN3uO_usT53JAsXp4Td8TcNu4kjXPgqTUbLJUG7220.cache +0 -0
  78. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/EF/EF9qrwbJ3DwrJmsImyKeW0iiA0yZ-AZ1IbNlYGuXnVs.cache +1 -0
  79. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/F3/F3BxEXMKcd4KO0HPnLrN1g6pJIJ_iQXCz9vW--uEixY.cache +1 -0
  80. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/FE/FEVuJQTU_YF19u2IzUvuuYlUOoKsyk4JA9kX5UCIelU.cache +2 -0
  81. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/Fi/FiX3vL70mZzXvdq98JwHM3hMmVjpV20ZRiPFXhrEw3A.cache +3 -0
  82. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/GG/GGT27f9SL2RsHpi8_pEJ1NHNgLBqDui3sDZOTgeD8z0.cache +0 -0
  83. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/GW/GWdxGb7VI1UP5TTDis3jju2Ep7-oqCWjNmRUDRTfgNI.cache +0 -0
  84. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/Gj/Gj2Go21wf2SZAnyJXKNSGfiGzYXAtrIdSHB2wuGrB6k.cache +0 -0
  85. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/Hi/HicniSJ1JdM-SmZBY-KtuXL6pubtiugbtOJ-qF07hfk.cache +1 -0
  86. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/Hm/HmvhHkuG82Y4O19j_mfQOw9Q4VEq5hW1Ny0Pzh3gY00.cache +3 -0
  87. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/Ic/IcQIS6wTKG3ZKN6foQYUMxNU4dNQRhrAdwVrAi8u11k.cache +0 -0
  88. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/Kz/KzqcEtJH4QmChDeq7W5WHCOe_NOjqNrAWOzOI0b0RrU.cache +0 -0
  89. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/LE/LE5Is_h8o9DKajRisizRsovSfpVc8T-Z_xScq0YxBMg.cache +1 -0
  90. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/LE/lEMT8eCBeJXLWA8GKsOLOQBRrEa21XylM_E5JCr4rgg.cache +1 -0
  91. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/LH/LHh7k1JlS1aIHCbv5fHLGjrvmMHH_Jz88vymBMjoCGk.cache +0 -0
  92. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/M2/M27EDDKK6_tC_Usl58dui_-zmw7oDqmzrrdQSIQY14Y.cache +1 -0
  93. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/M3/M34sNs1L2xLkDoLgxPZpTl1b_2gnlNorXfetzSSNFzQ.cache +0 -0
  94. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/MK/MKsj2fh8ePsAqeFVg9gjrj5TFKHFMhkpv3847HEtRCQ.cache +1 -0
  95. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/ML/MLV0jF0Mx0KJXC8UlPpWgJnHnCDmoZGYwvIY8iCScUo.cache +2 -0
  96. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/MM/MM4lzeaS_hxItA00rs1BMB2FiTSDW11M9YXnPe-tTis.cache +0 -0
  97. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/Mc/Mcvqw-yzpBv2GpUMmKfPZed-6wXUYd8rDdGJ12xAEsA.cache +0 -0
  98. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/Ng/Ng6PnQ3Vuj0_W2vnfOllxbQzYvtwYSP-2fJkIBNIQp4.cache +3 -0
  99. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/Nn/NneCOb3TsKS1FT6ImmBbFe5UuI_LxAmaYyk5o3st8jc.cache +1 -0
  100. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/Np/NpFyXvvOaE4AgXrrlhA8mGz0Zu3wiafGirgwSdbZdxM.cache +0 -0
  101. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/Nv/NvbZ3Hlpr70GgSBXU-Zu8vuInq35MP6A63TQASVMfr8.cache +2 -0
  102. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/O6/O6ubwnCL6c5Yy8zI3admUI6VD1-5gDM8cbdeT2iBZ_Y.cache +2 -0
  103. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/OH/OHcmhT_tBVMS3zWpgTX93KyKUXBtbWDJsoFzXiYnhVc.cache +3 -0
  104. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/Ro/RogqNsDfrMolvoqWLn8lCbweCzxIhN-fgXnLfBMFo8A.cache +0 -0
  105. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/Rq/RqtRjsxCy4uH845AVedRm0uhgG7RXL11Z72EYSDplIs.cache +1 -0
  106. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/Rw/RwnfCAubyGSPbqkRrfgNdC6gRBBVWkqYUodv_-hpQ7w.cache +0 -0
  107. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/T9/T9uigSdiu4wxaUieqeBq4uQYRdwGsT2acN15p0osfMU.cache +3 -0
  108. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/TH/THqp0Tr7zg3pz3WISBe0_u-QcPXHIesH0WlH_6zKzco.cache +1 -0
  109. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/VF/VFaPzl3--nBqsNzvUgJUt2TjANPLzaelSx_eOc_J090.cache +1 -0
  110. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/VY/VYQF15XEQNHgoXSorUWaNjqn-HSfupp3CZtdyCL7ymU.cache +0 -0
  111. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/Vw/VwDJ5qrChK2CUSSnAwxLWSuLNUaForre3ZRxtFlW4Ac.cache +1 -0
  112. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/WI/WIYh9Gi9ndC88dKHN_bO8iFiJwSzUGaJI9gnhSO7Ji8.cache +0 -0
  113. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/X5/X5dT1Gdr8sbGtvrNlwW86aVIMgR2zTd-2kFQ9C3L7Wg.cache +0 -0
  114. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/XG/XG0dD9kx-qBYVC-WwhzECOTsSICqvnAqLOCq1JWWrRI.cache +2 -0
  115. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/Xo/XoLrhpxlw0kg9u3bMnDyQen8HXO2rqPRTCb8NYObL8Y.cache +0 -0
  116. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/Y1/Y1VBjwUHneW8QFgIGPmxOM0Ug3nZUTAUKXzkKah9AOs.cache +0 -0
  117. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/ZT/ZTJFqWngu6gOr5GuiqAV52YNHRuJeuwQelNDhtm8pKU.cache +1 -0
  118. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/ZT/zT_x9gWE8eLO9s8Ul_dtwvAqzekg3DpeuSRkQepkKJ4.cache +2 -0
  119. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/_t/_tr5uJ_M5iBr2N9uBonXFmxfqt1TxbYfFCxnfAiyiNU.cache +1 -0
  120. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/_w/_wDP8qm_JkjY5FPjc6RNCiP_dc_uBNRMyH64Sl8LJzY.cache +1 -0
  121. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/aW/aWSe6QsJ8Dk7ZAuTOYgjegrAoC2XYQtCCeo9KVzVVQs.cache +0 -0
  122. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/ap/apMxRwM7mSeCyECnYWAbxnWbsfoJLdJfwaxudP9Chi8.cache +1 -0
  123. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/at/ataSdIHBESkY5GN7sMG-XyOftCkIWDK0QaKEewVkqIU.cache +0 -0
  124. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/au/aukwvh3eZhFFGyc5qZWT9KMJQHxFrTDEqfjXr4VM4dc.cache +3 -0
  125. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/bu/bu6XdfzbXfiTnSQ_6Shml3tpB7ovoUAcuCGNs-eYehI.cache +3 -0
  126. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/c2/c2yubRDZmdi6tFc__QitGtUTkpKt0Goc7FNKhRNw0Uk.cache +2 -0
  127. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/cm/cm54PMT0IiaHCIjq6T3ZTIMm7Yxo42XwnsyOgnCOXiM.cache +3 -0
  128. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/cm/cmUPiFXFgGEC8LObv4RZX5lwwWHkwS9sqRq1o37epy8.cache +2 -0
  129. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/dJ/dJn0Pwx5cPYGC5q_eHDfNfFt4JiC-fmQVaH5myKr0VA.cache +3 -0
  130. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/dq/dq3gHEjKxw3E013mazOcCiXNbDdPTz-A8oh-ehBXeLY.cache +2 -0
  131. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/em/emsg06Cc17Q7p1Y0IO1PFRtnyhMbj1u4-wVx6W8bgJs.cache +2 -0
  132. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/fu/fuVeiMpLNyElLwjOR6EBQP7-ZUwuJTsQe_Ie11TlX7Y.cache +0 -0
  133. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/hY/hY9drCAzyxN-KBhXav5VLmk4-AFlKT2yguKNLm7wGsA.cache +3 -0
  134. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/hg/hgkvHB4OV3E0Gq-Ud_lH2ljTzyb-8nGquMAFTBr4O0o.cache +2 -0
  135. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/iF/iF-qi7hkpBxivxIEuDhEbYCCUdo723aaitG7rholzuY.cache +3 -0
  136. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/iF/if_9bC6Uj3ZokQmIk9el665rc-6LTBoL6U4qR7gtoJU.cache +1 -0
  137. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/j1/j1GLMjXSyzOpsUCKPd35AfXExAXEMHejQdvPd8UErkQ.cache +1 -0
  138. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/j3/j3e_BmEvAiJhmSyXAU9VAE_CZACcKEbzRxeshc8FhJs.cache +0 -0
  139. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/jV/JVKq8sER4GK2hEv9E7tZ7mvXxh14vJeTVCDEnZC0JaM.cache +0 -0
  140. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/jV/jVXcSpsjtyBHtJaFkxwLVcVm79OT5CVEA9XuzV01O2o.cache +2 -0
  141. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/jn/jnZ8_zeT39edfDrXllxCW_oH5T-ynBUYGT8YNgyFU9c.cache +2 -0
  142. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/kK/kKAy2xIb8ozBaCVqr7LkIsO3-78l5U08uq1Ot7JvES0.cache +0 -0
  143. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/kU/kUed8S8DBmWvnTdMtg0PzTYj51Rbq9R9yfr6bhPzp2I.cache +1 -0
  144. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/kw/kwNFhW24nxXy_HfVKYdXJyRqhu7ljgl9wR2DcJUo-nA.cache +0 -0
  145. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/ls/ls-nV5B50MgpIYfcYZWWWjiNsU2tD2DrEbiiEmGtArk.cache +0 -0
  146. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/m4/m47fbWgjCvvfU0XLMaGJE1zb9zh2STuMZFp7umYjLTA.cache +1 -0
  147. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/m_/m_VPZpr_Dhvko_jzP2-4VRhDpknOtciX8pqAq3jY4P8.cache +3 -0
  148. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/n5/n5rslESPawJxj7Ii3QNWxFl-2sUJE4leiEyzbuFE9Mc.cache +1 -0
  149. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/nz/nZA-tpBPvlKlQKIbdV4_4NL_5GSce7A27-uejTdXxyo.cache +0 -0
  150. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/nz/nZH58NGgTuROXOSPMik3s7SjDoBucsgev57n0u9vlPA.cache +2 -0
  151. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/nz/nzkJWHKhKCtwI9w3TBcwyGzKEik4UFYwulf2jO7xPXc.cache +2 -0
  152. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/o4/o42lMuA3eKF-eY5RsJO4kHIGfba2QHOCO0b9rz5iNIU.cache +0 -0
  153. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/oF/oFrXjOEKX9hseSPY19vWwGHfET4QpdjGD5UdFIEN8mo.cache +0 -0
  154. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/od/od3K7nGVNq7YsrTVF7ItWWkHs8F_XEGn_BlCD5wW_gc.cache +1 -0
  155. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/qB/qBg00KcV6F7tmpcxNC5tjzrdC3FWusHvOGhz1aV0zs8.cache +1 -0
  156. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/qZ/qZuwDOadKdP81QKNPTbE4G8CESQEnmrIE_INtDzerso.cache +1 -0
  157. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/qo/qoEv51qzi0h4mpNP5B4vQydW2FmhC_vbbx6vWJ99Jls.cache +1 -0
  158. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/sR/sRyvthBFPCFQfQ75moJFSYPaR71b7XeQRo9UwjvCWFc.cache +0 -0
  159. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/sl/sliQa4_eurm9yEqcshtJrfPPE6M-DF-t8REXVSOfyUQ.cache +1 -0
  160. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/tA/tAZH91Tm6zrf_sm00veerMt3qNaM2uC74u4Zuxv2gg0.cache +1 -0
  161. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/tQ/tQ-nAfm23F-ckwkWPuftbJS6tumDQhNxQgPIZSrfPM0.cache +1 -0
  162. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/tr/trn1zjMZoBOIgSYgI84OzddhfICB2KnU4XbV8axLVGo.cache +1 -0
  163. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/u3/u3VRW5zPBM7BcNRi3G4B8M6CdscKSLJ3xYmp44ekmjU.cache +0 -0
  164. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/uG/UgcLSZ8AKbE1AYf75cSUXI6uefPECNfBcVQ7L0BDzHg.cache +3 -0
  165. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/uG/uGhNDzX7DxymN3xqHLHTndSae3bN0A-MlTk05m6wV4A.cache +1 -0
  166. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/uY/uYA487PT8yb9JEmcxwo8syJu8nzceyQ2LExeSnyVu50.cache +1 -0
  167. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/vg/vgD37dvBvogssYiXikQ4Olm53D_b46IIW7PB9aAcCVM.cache +0 -0
  168. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/w2/w2_uibK0i1njL5U_7brQZhib5VYdXXvpvhE06yX1SqE.cache +1 -0
  169. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/wF/wFT7zoeQQwz-V8syWaS01xr7xgZWr3UNQLWbYSKXirw.cache +1 -0
  170. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/wN/wNw4kAG0E_RrvLeFAizMmTp_0jh-HGw1ONLtRyw92z0.cache +0 -0
  171. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/x-/x-qCUFpNxK1jTHH2qV_VgVk_KrGKdHzNGOnDMVFstuQ.cache +1 -0
  172. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/xA/xAEcSwhjIYJzUW_4aTWYVRUc7moZTTeCKKkqkxLhrfg.cache +0 -0
  173. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/xn/xnt4dCDEEL7zX5DqWMpQag3bopptMKMDNFzvfvF5OPs.cache +1 -0
  174. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/yI/yI2MBOO01NRSHWPeRJfC5SFBMpefo4xeYfHeWEvkdks.cache +1 -0
  175. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/yk/ykU9I1MzGh7clO4pu853ZKl4LtMlDtyr8BjpiozFQ8E.cache +2 -0
  176. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/yk/ykWLbyEuqa0JHcwHJ-SdVxv4ImwnymlVxvbPKtk6HUM.cache +1 -0
  177. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/zO/zOr1jCikukgv8-bNHvK43vGLa3CSjtmiwf2K7dUjTnw.cache +0 -0
  178. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/z_/z_2HCE5ElxkvIdf9isrro0NsgSdEU0oRSalWcYQss74.cache +1 -0
  179. data/spec/job_spec.rb +194 -0
  180. data/spec/models/crono/crono_job_spec.rb +31 -0
  181. data/spec/performer_proxy_spec.rb +45 -0
  182. data/spec/period_spec.rb +141 -0
  183. data/spec/rails_helper.rb +89 -0
  184. data/spec/scheduler_spec.rb +62 -0
  185. data/spec/spec_helper.rb +19 -0
  186. data/spec/tasks/crono_tasks_spec.rb +23 -0
  187. data/spec/web_spec.rb +61 -0
  188. metadata +355 -68
  189. data/.gitignore +0 -10
  190. data/.rspec +0 -2
  191. data/.travis.yml +0 -14
  192. data/Changes.md +0 -88
  193. data/Gemfile +0 -4
  194. data/Gemfile.lock +0 -78
  195. data/NOTICE +0 -2
  196. data/README.md +0 -218
  197. data/bin/console +0 -14
  198. data/bin/setup +0 -7
  199. data/crono.gemspec +0 -34
  200. data/examples/crono_web_ui.png +0 -0
  201. data/examples/cronotab.rb +0 -14
  202. data/examples/monitrc.conf +0 -6
  203. data/lib/crono/web.rb +0 -22
  204. data/web/assets/css/custom.css +0 -19
  205. data/web/assets/css/materialize.min.css +0 -16
  206. data/web/assets/font/material-design-icons/LICENSE.txt +0 -428
  207. data/web/assets/font/material-design-icons/Material-Design-Icons.eot +0 -0
  208. data/web/assets/font/material-design-icons/Material-Design-Icons.svg +0 -751
  209. data/web/assets/font/material-design-icons/Material-Design-Icons.ttf +0 -0
  210. data/web/assets/font/material-design-icons/Material-Design-Icons.woff +0 -0
  211. data/web/assets/font/material-design-icons/Material-Design-Icons.woff2 +0 -0
  212. data/web/assets/font/roboto/Roboto-Bold.ttf +0 -0
  213. data/web/assets/font/roboto/Roboto-Bold.woff +0 -0
  214. data/web/assets/font/roboto/Roboto-Bold.woff2 +0 -0
  215. data/web/assets/font/roboto/Roboto-Light.ttf +0 -0
  216. data/web/assets/font/roboto/Roboto-Light.woff +0 -0
  217. data/web/assets/font/roboto/Roboto-Light.woff2 +0 -0
  218. data/web/assets/font/roboto/Roboto-Medium.ttf +0 -0
  219. data/web/assets/font/roboto/Roboto-Medium.woff +0 -0
  220. data/web/assets/font/roboto/Roboto-Medium.woff2 +0 -0
  221. data/web/assets/font/roboto/Roboto-Regular.ttf +0 -0
  222. data/web/assets/font/roboto/Roboto-Regular.woff +0 -0
  223. data/web/assets/font/roboto/Roboto-Regular.woff2 +0 -0
  224. data/web/assets/font/roboto/Roboto-Thin.ttf +0 -0
  225. data/web/assets/font/roboto/Roboto-Thin.woff +0 -0
  226. data/web/assets/font/roboto/Roboto-Thin.woff2 +0 -0
  227. data/web/assets/js/jquery-2.1.3.min.js +0 -4
  228. data/web/assets/js/materialize.min.js +0 -10
  229. data/web/views/dashboard.haml +0 -36
  230. data/web/views/job.haml +0 -15
  231. data/web/views/layout.haml +0 -29
data/spec/job_spec.rb ADDED
@@ -0,0 +1,194 @@
1
+ require 'spec_helper'
2
+
3
+ class TestJob
4
+ def perform(*args)
5
+ puts 'Test!'
6
+ end
7
+ end
8
+
9
+ class TestFailingJob
10
+ def perform(*args)
11
+ raise 'Some error'
12
+ end
13
+ end
14
+
15
+ class TestNoArgsJob
16
+ def perform
17
+ puts 'Test!'
18
+ end
19
+ end
20
+
21
+ describe Crono::Job do
22
+ let(:period) { Crono::Period.new(2.day, at: '15:00') }
23
+ let(:job_args) {[{some: 'data'}]}
24
+ let(:job) { Crono::Job.new(TestJob, period, []) }
25
+ let(:job_with_args) { Crono::Job.new(TestJob, period, job_args) }
26
+ let(:failing_job) { Crono::Job.new(TestFailingJob, period, []) }
27
+ let(:not_args_job) { Crono::Job.new(TestJob, period) }
28
+
29
+ it 'should contain performer and period' do
30
+ expect(job.performer).to be TestJob
31
+ expect(job.period).to be period
32
+ end
33
+
34
+ it 'should contain data as JSON String' do
35
+ expect(job_with_args.job_args).to eq '[{"some":"data"}]'
36
+ end
37
+
38
+ describe '#next' do
39
+ it 'should return next performing time according to period' do
40
+ expect(job.next).to be_eql period.next
41
+ end
42
+ end
43
+
44
+ describe '#perform' do
45
+ after { job.send(:model).destroy }
46
+
47
+ it 'should run performer in separate thread' do
48
+ expect(job).to receive(:save)
49
+ thread = job.perform.join
50
+ expect(thread).to be_stop
51
+ end
52
+
53
+ it 'should save performin errors to log' do
54
+ thread = failing_job.perform.join
55
+ expect(thread).to be_stop
56
+ saved_log = Crono::CronoJob.find_by(job_id: failing_job.job_id).log
57
+ expect(saved_log).to include 'Some error'
58
+ end
59
+
60
+ it 'should set Job#healthy to false if perform with error' do
61
+ failing_job.perform.join
62
+ expect(failing_job.healthy).to be false
63
+ end
64
+
65
+ it 'should execute one' do
66
+ job.execution_interval = 5.minutes
67
+
68
+ expect(job).to receive(:perform_job).once
69
+ job.perform.join
70
+ thread = job.perform.join
71
+ expect(thread).to be_stop
72
+ end
73
+
74
+ it 'should execute twice' do
75
+ job.execution_interval = 0.minutes
76
+
77
+ test_preform_job_twice
78
+ end
79
+
80
+ it 'should execute twice without args' do
81
+ test_preform_job_twice not_args_job
82
+ end
83
+
84
+ it 'should execute twice without initialize execution_interval' do
85
+ test_preform_job_twice
86
+ end
87
+
88
+ it 'should call perform of performer' do
89
+ expect(TestJob).to receive(:new).with(no_args)
90
+ thread = job.perform.join
91
+ expect(thread).to be_stop
92
+ end
93
+
94
+ it 'should call perform of performer with data' do
95
+ test_job = double()
96
+ expect(TestJob).to receive(:new).and_return(test_job)
97
+ expect(test_job).to receive(:perform).with([{ 'some' => 'data' }])
98
+ thread = job_with_args.perform.join
99
+ expect(thread).to be_stop
100
+ end
101
+
102
+ def test_preform_job_twice(jon_instance = job)
103
+ expect(jon_instance).to receive(:perform_job).twice
104
+ jon_instance.perform.join
105
+ thread = jon_instance.perform.join
106
+ expect(thread).to be_stop
107
+ end
108
+ end
109
+
110
+ describe '#description' do
111
+ it 'should return job identificator' do
112
+ expect(job.description).to be_eql('Perform TestJob every 2 days at 15:00')
113
+ end
114
+ end
115
+
116
+ describe '#save' do
117
+ it 'should save new job to DB' do
118
+ expect(Crono::CronoJob.where(job_id: job.job_id)).to_not exist
119
+ job.save
120
+ expect(Crono::CronoJob.where(job_id: job.job_id)).to exist
121
+ end
122
+
123
+ it 'should update saved job' do
124
+ job.last_performed_at = Time.zone.now
125
+ job.healthy = true
126
+ job.job_args = JSON.generate([{some: 'data'}])
127
+ job.save
128
+ @crono_job = Crono::CronoJob.find_by(job_id: job.job_id)
129
+ expect(@crono_job.last_performed_at.utc.to_s).to be_eql job.last_performed_at.utc.to_s
130
+ expect(@crono_job.healthy).to be true
131
+ end
132
+
133
+ it 'should save log' do
134
+ message = 'test message'
135
+ job.send(:log, message)
136
+ job.save
137
+ expect(job.send(:model).reload.log).to include message
138
+ expect(job.job_log.string).to be_empty
139
+ end
140
+
141
+ it 'should not truncate log if not specified' do
142
+ log = (1..100).map {|n| "line #{n}" }.join("\n")
143
+ job = Crono::Job.new(TestJob, period, [])
144
+ job.send(:log, log)
145
+ job.save
146
+ expect(job.send(:model).reload.log.lines.size).to be >= log.lines.size
147
+ end
148
+
149
+ it 'should truncate log if specified' do
150
+ log = (1..100).map {|n| "line #{n}" }.join("\n")
151
+ job = Crono::Job.new(TestJob, period, [], truncate_log: 50)
152
+ job.send(:log, log)
153
+ job.save
154
+ expect(job.send(:model).reload.log.lines.size).to be 50
155
+ end
156
+ end
157
+
158
+ describe '#load' do
159
+ before do
160
+ @saved_last_performed_at = job.last_performed_at = Time.zone.now
161
+ job.save
162
+ end
163
+
164
+ it 'should load last_performed_at from DB' do
165
+ @job = Crono::Job.new(TestJob, period, job_args)
166
+ @job.load
167
+ expect(@job.last_performed_at.utc.to_s).to be_eql @saved_last_performed_at.utc.to_s
168
+ end
169
+ end
170
+
171
+ describe '#log' do
172
+ it 'should write log messages to both common and job log' do
173
+ message = 'Test message'
174
+ job.logfile = "/dev/null"
175
+ expect(job.logger).to receive(:log).with(Logger::INFO, message)
176
+ expect(job.job_logger).to receive(:log).with(Logger::INFO, message)
177
+ job.send(:log, message)
178
+ end
179
+
180
+ it 'should write job log to Job#job_log' do
181
+ message = 'Test message'
182
+ job.send(:log, message)
183
+ expect(job.job_log.string).to include(message)
184
+ end
185
+ end
186
+
187
+ describe '#log_error' do
188
+ it 'should call log with ERROR severity' do
189
+ message = 'Test message'
190
+ expect(job).to receive(:log).with(message, Logger::ERROR)
191
+ job.send(:log_error, message)
192
+ end
193
+ end
194
+ end
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+ require 'rack/test'
3
+
4
+ module Crono
5
+ RSpec.describe Crono::CronoJob, type: :model do
6
+ let(:valid_attrs) do
7
+ {
8
+ job_id: 'Perform TestJob every 3 days'
9
+ }
10
+ end
11
+
12
+ it 'should validate presence of job_id' do
13
+ @crono_job = Crono::CronoJob.new
14
+ expect(@crono_job).not_to be_valid
15
+ expect(@crono_job.errors.added?(:job_id, :blank)).to be true
16
+ end
17
+
18
+ it 'should validate uniqueness of job_id' do
19
+ Crono::CronoJob.create!(job_id: 'TestJob every 2 days')
20
+ @crono_job = Crono::CronoJob.create(job_id: 'TestJob every 2 days')
21
+ expect(@crono_job).not_to be_valid
22
+ expect(@crono_job.errors.size).to be 1
23
+ end
24
+
25
+ it 'should save job_id to DB' do
26
+ Crono::CronoJob.create!(valid_attrs)
27
+ @crono_job = Crono::CronoJob.find_by(job_id: valid_attrs[:job_id])
28
+ expect(@crono_job).to be_present
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,45 @@
1
+ require 'spec_helper'
2
+
3
+ class TestJob
4
+ def perform
5
+ puts 'Test!'
6
+ end
7
+ end
8
+
9
+ describe Crono::PerformerProxy do
10
+ it 'should add job to schedule' do
11
+ expect(Crono.scheduler).to receive(:add_job).with(kind_of(Crono::Job))
12
+ Crono.perform(TestJob).every(2.days, at: '15:30')
13
+ end
14
+
15
+ it 'should set execution interval' do
16
+ allow(Crono).to receive(:scheduler).and_return(Crono::Scheduler.new)
17
+ expect_any_instance_of(Crono::Job).to receive(:execution_interval=).with(0.minutes).once
18
+ expect_any_instance_of(Crono::Job).to receive(:execution_interval=).with(10.minutes).once
19
+ Crono.perform(TestJob).every(2.days, at: '15:30').once_per 10.minutes
20
+ end
21
+
22
+ it 'do nothing when job not initalized' do
23
+ expect_any_instance_of(Crono::Job).not_to receive(:execution_interval=)
24
+ expect_any_instance_of(described_class).to receive(:once_per)
25
+ Crono.perform(TestJob).once_per 10.minutes
26
+ end
27
+
28
+ it 'should add job with args to schedule' do
29
+ expect(Crono::Job).to receive(:new).with(TestJob, kind_of(Crono::Period), :some, { some: 'data' })
30
+ allow(Crono.scheduler).to receive(:add_job)
31
+ Crono.perform(TestJob, :some, { some: 'data' }).every(2.days, at: '15:30')
32
+ end
33
+
34
+ it 'should add job without args to schedule' do
35
+ expect(Crono::Job).to receive(:new).with(TestJob, kind_of(Crono::Period), nil, nil)
36
+ allow(Crono.scheduler).to receive(:add_job)
37
+ Crono.perform(TestJob).every(2.days, at: '15:30')
38
+ end
39
+
40
+ it 'should add job with options to schedule' do
41
+ expect(Crono::Job).to receive(:new).with(TestJob, kind_of(Crono::Period), nil, { some_option: true })
42
+ allow(Crono.scheduler).to receive(:add_job)
43
+ Crono.perform(TestJob).with_options(some_option: true).every(2.days, at: '15:30')
44
+ end
45
+ end
@@ -0,0 +1,141 @@
1
+ require 'spec_helper'
2
+
3
+ describe Crono::Period do
4
+ describe '#description' do
5
+ it 'should return period description' do
6
+ @period = Crono::Period.new(1.week, on: :monday, at: '15:20')
7
+ expected_description = if ActiveSupport::VERSION::MAJOR >= 5
8
+ 'every 1 week at 15:20 on Monday'
9
+ else
10
+ 'every 7 days at 15:20 on Monday'
11
+ end
12
+ expect(@period.description).to be_eql(expected_description)
13
+ end
14
+ end
15
+
16
+ describe '#next' do
17
+ context 'in weakly basis' do
18
+ it "should raise error if 'on' is wrong" do
19
+ expect { @period = Crono::Period.new(7.days, on: :bad_day) }
20
+ .to raise_error("Wrong 'on' day")
21
+ end
22
+
23
+ it 'should raise error when period is less than 1 week' do
24
+ expect { @period = Crono::Period.new(6.days, on: :monday) }
25
+ .to raise_error("period should be at least 1 week to use 'on'")
26
+ end
27
+
28
+ it "should return a 'on' day" do
29
+ @period = Crono::Period.new(1.week, on: :thursday, at: '15:30')
30
+ current_week = Time.zone.now.beginning_of_week
31
+ last_run_time = current_week.advance(days: 1) # last run on the tuesday
32
+ next_run_at = Time.zone.now.next_week.advance(days: 3)
33
+ .change(hour: 15, min: 30)
34
+ expect(@period.next(since: last_run_time)).to be_eql(next_run_at)
35
+ end
36
+
37
+ it "should return a next week day 'on'" do
38
+ @period = Crono::Period.new(1.week, on: :thursday)
39
+ Timecop.freeze(Time.zone.now.beginning_of_week.advance(days: 4)) do
40
+ expect(@period.next).to be_eql(Time.zone.now.next_week.advance(days: 3))
41
+ end
42
+ end
43
+
44
+ it 'should return a current week day on the first run if not too late' do
45
+ @period = Crono::Period.new(7.days, on: :tuesday)
46
+ beginning_of_the_week = Time.zone.now.beginning_of_week
47
+ tuesday = beginning_of_the_week.advance(days: 1)
48
+ Timecop.freeze(beginning_of_the_week) do
49
+ expect(@period.next).to be_eql(tuesday)
50
+ end
51
+ end
52
+
53
+ it 'should return today on the first run if not too late' do
54
+ @period = Crono::Period.new(1.week, on: :sunday, at: '22:00')
55
+ Timecop.freeze(Time.zone.now.beginning_of_week.advance(days: 6)
56
+ .change(hour: 21, min: 0)) do
57
+ expect(@period.next).to be_eql(
58
+ Time.zone.now.beginning_of_week.advance(days: 6).change(hour: 22, min: 0)
59
+ )
60
+ end
61
+ end
62
+ end
63
+
64
+ context 'in daily basis' do
65
+ it "should return Time.zone.now if the next time in past" do
66
+ @period = Crono::Period.new(1.day, at: '06:00')
67
+ expect(@period.next(since: 2.days.ago).to_s).to be_eql(Time.zone.now.to_s)
68
+ end
69
+
70
+ it 'should return time 2 days from now' do
71
+ @period = Crono::Period.new(2.day)
72
+ expect(@period.next.to_s).to be_eql(2.days.from_now.to_s)
73
+ end
74
+
75
+ it "should set time to 'at' time as a string" do
76
+ time = 10.minutes.ago
77
+ at = [time.hour, time.min].join(':')
78
+ @period = Crono::Period.new(2.days, at: at)
79
+ expect(@period.next.to_s).to be_eql(2.days.from_now.change(hour: time.hour, min: time.min).to_s)
80
+ end
81
+
82
+ it "should set time to 'at' time as a hash" do
83
+ time = 10.minutes.ago
84
+ at = { hour: time.hour, min: time.min }
85
+ @period = Crono::Period.new(2.days, at: at)
86
+ expect(@period.next.to_s).to be_eql(2.days.from_now.change(at).to_s)
87
+ end
88
+
89
+ it "should raise error when 'at' is wrong" do
90
+ expect {
91
+ Crono::Period.new(2.days, at: 1)
92
+ }.to raise_error("Unknown 'at' format")
93
+ end
94
+
95
+ it 'should raise error when period is less than 1 day' do
96
+ expect {
97
+ Crono::Period.new(5.hours, at: '15:30')
98
+ }.to raise_error("period should be at least 1 day to use 'at' with specified hour")
99
+ end
100
+
101
+ it 'should return time in relation to last time' do
102
+ @period = Crono::Period.new(2.days)
103
+ expect(@period.next(since: 1.day.ago).to_s).to be_eql(1.day.from_now.to_s)
104
+ end
105
+
106
+ it 'should return today time if it is first run and not too late' do
107
+ time = 10.minutes.from_now
108
+ at = { hour: time.hour, min: time.min }
109
+ @period = Crono::Period.new(2.days, at: at)
110
+ expect(@period.next.utc.to_s).to be_eql(Time.zone.now.change(at).utc.to_s)
111
+ end
112
+ end
113
+
114
+ context 'in hourly basis' do
115
+ it 'should return next hour minutes if current hour minutes passed' do
116
+ Timecop.freeze(Time.zone.now.beginning_of_hour.advance(minutes: 20)) do
117
+ @period = Crono::Period.new(1.hour, at: { min: 15 })
118
+ expect(@period.next.utc.to_s).to be_eql 1.hour.from_now.beginning_of_hour.advance(minutes: 15).utc.to_s
119
+ end
120
+ end
121
+
122
+ it 'should return current hour minutes if current hour minutes not passed yet' do
123
+ Timecop.freeze(Time.zone.now.beginning_of_hour.advance(minutes: 10)) do
124
+ @period = Crono::Period.new(1.hour, at: { min: 15 })
125
+ expect(@period.next.utc.to_s).to be_eql Time.zone.now.beginning_of_hour.advance(minutes: 15).utc.to_s
126
+ end
127
+ end
128
+
129
+ it 'should return next hour minutes within the given interval' do
130
+ Timecop.freeze(Time.zone.now.change(hour: 16, min: 10)) do
131
+ @period = Crono::Period.new(1.hour, at: { min: 15 }, within: '08:00-16:00')
132
+ expect(@period.next.utc.to_s).to be_eql Time.zone.now.tomorrow.change(hour: 8, min: 15).utc.to_s
133
+ end
134
+ Timecop.freeze(Time.zone.now.change(hour: 16, min: 10)) do
135
+ @period = Crono::Period.new(1.hour, at: { min: 15 }, within: '23:00-07:00')
136
+ expect(@period.next.utc.to_s).to be_eql Time.zone.now.change(hour: 23, min: 15).utc.to_s
137
+ end
138
+ end
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,89 @@
1
+ # This file is copied to spec/ when you run 'rails generate rspec:install'
2
+ require 'spec_helper'
3
+ ENV['RAILS_ENV'] ||= 'test'
4
+ require File.expand_path('../spec/internal/config/environment.rb', __dir__)
5
+ # Prevent database truncation if the environment is production
6
+ abort('The Rails environment is running in production mode!') if Rails.env.production?
7
+ require 'rspec/rails'
8
+ # Add additional requires below this line. Rails is not loaded until this point!
9
+ require 'bundler/setup'
10
+ Bundler.setup
11
+
12
+ $LOAD_PATH.unshift File.expand_path('../../lib', __dir__)
13
+
14
+ require 'timecop'
15
+ require 'byebug'
16
+ require 'crono'
17
+
18
+ # setting default time zone
19
+ # In Rails project, Time.zone_default equals "UTC"
20
+ Time.zone_default = Time.find_zone('UTC')
21
+
22
+ ActiveRecord::Base.logger = Logger.new($stdout)
23
+
24
+ ActiveRecord::Base.establish_connection(
25
+ adapter: 'sqlite3',
26
+ database: ':memory'
27
+ )
28
+ ActiveRecord::Schema.define do
29
+ require_relative '../lib/generators/crono/install/templates/migrations/create_crono_jobs.rb'
30
+ @migration_version = '[6.1]'
31
+ end
32
+ CreateCronoJobs.up
33
+
34
+
35
+ # Requires supporting ruby files with custom matchers and macros, etc, in
36
+ # spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are
37
+ # run as spec files by default. This means that files in spec/support that end
38
+ # in _spec.rb will both be required and run as specs, causing the specs to be
39
+ # run twice. It is recommended that you do not name files matching this glob to
40
+ # end with _spec.rb. You can configure this pattern with the --pattern
41
+ # option on the command line or in ~/.rspec, .rspec or `.rspec-local`.
42
+ #
43
+ # The following line is provided for convenience purposes. It has the downside
44
+ # of increasing the boot-up time by auto-requiring all files in the support
45
+ # directory. Alternatively, in the individual `*_spec.rb` files, manually
46
+ # require only the support files necessary.
47
+ #
48
+ # Dir[Rails.root.join('spec', 'support', '**', '*.rb')].sort.each { |f| require f }
49
+
50
+ # Checks for pending migrations and applies them before tests are run.
51
+ # If you are not using ActiveRecord, you can remove these lines.
52
+ begin
53
+ ActiveRecord::Migration.maintain_test_schema!
54
+ rescue ActiveRecord::PendingMigrationError => e
55
+ puts e.to_s.strip
56
+ exit 1
57
+ end
58
+ RSpec.configure do |config|
59
+ # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
60
+ # config.fixture_path = "#{::Rails.root}/spec/fixtures"
61
+
62
+ # If you're not using ActiveRecord, or you'd prefer not to run each of your
63
+ # examples within a transaction, remove the following line or assign false
64
+ # instead of true.
65
+ # config.use_transactional_fixtures = true
66
+
67
+ # You can uncomment this line to turn off ActiveRecord support entirely.
68
+ # config.use_active_record = false
69
+
70
+ # RSpec Rails can automatically mix in different behaviours to your tests
71
+ # based on their file location, for example enabling you to call `get` and
72
+ # `post` in specs under `spec/controllers`.
73
+ #
74
+ # You can disable this behaviour by removing the line below, and instead
75
+ # explicitly tag your specs with their type, e.g.:
76
+ #
77
+ # RSpec.describe UsersController, type: :controller do
78
+ # # ...
79
+ # end
80
+ #
81
+ # The different available types are documented in the features, such as in
82
+ # https://relishapp.com/rspec/rspec-rails/docs
83
+ config.infer_spec_type_from_file_location!
84
+
85
+ # Filter lines from Rails gems in backtraces.
86
+ config.filter_rails_from_backtrace!
87
+ # arbitrary gems may also be filtered via:
88
+ # config.filter_gems_from_backtrace("gem name")
89
+ end
@@ -0,0 +1,62 @@
1
+ require 'spec_helper'
2
+
3
+ class TestJob
4
+ def perform
5
+ puts 'Test!'
6
+ end
7
+ end
8
+
9
+ describe Crono::Scheduler do
10
+ let(:scheduler) { Crono::Scheduler.new }
11
+
12
+ describe '#add_job' do
13
+ it 'should call Job#load on Job' do
14
+ @job = Crono::Job.new(TestJob, Crono::Period.new(10.day, at: '04:05'), [])
15
+ expect(@job).to receive(:load)
16
+ scheduler.add_job(@job)
17
+ end
18
+ end
19
+
20
+ describe '#next_jobs' do
21
+ it 'should return next job in schedule' do
22
+ scheduler.jobs = jobs = [
23
+ Crono::Period.new(3.days, at: 10.minutes.from_now.strftime('%H:%M')),
24
+ Crono::Period.new(1.day, at: 20.minutes.from_now.strftime('%H:%M')),
25
+ Crono::Period.new(7.days, at: 40.minutes.from_now.strftime('%H:%M'))
26
+ ].map { |period| Crono::Job.new(TestJob, period, []) }
27
+
28
+ time, jobs = scheduler.next_jobs
29
+ expect(jobs).to be_eql [jobs[0]]
30
+ end
31
+
32
+ it 'should return an array of jobs scheduled at same time with `at`' do
33
+ time = 5.minutes.from_now
34
+ scheduler.jobs = jobs = [
35
+ Crono::Period.new(1.day, at: time.strftime('%H:%M')),
36
+ Crono::Period.new(1.day, at: time.strftime('%H:%M')),
37
+ Crono::Period.new(1.day, at: 10.minutes.from_now.strftime('%H:%M'))
38
+ ].map { |period| Crono::Job.new(TestJob, period, []) }
39
+
40
+ time, jobs = scheduler.next_jobs
41
+ expect(jobs).to be_eql [jobs[0], jobs[1]]
42
+ end
43
+
44
+ it 'should handle a few jobs scheduled at same time without `at`' do
45
+ scheduler.jobs = jobs = [
46
+ Crono::Period.new(10.seconds),
47
+ Crono::Period.new(10.seconds),
48
+ Crono::Period.new(1.day, at: 10.minutes.from_now.strftime('%H:%M'))
49
+ ].map { |period| Crono::Job.new(TestJob, period, []) }
50
+
51
+ _, next_jobs = scheduler.next_jobs
52
+ expect(next_jobs).to be_eql [jobs[0]]
53
+
54
+ Timecop.travel(4.seconds.from_now)
55
+ expect(Thread).to receive(:new)
56
+ jobs[0].perform
57
+
58
+ _, next_jobs = scheduler.next_jobs
59
+ expect(next_jobs).to be_eql [jobs[1]]
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,19 @@
1
+ require 'bundler'
2
+
3
+ Bundler.require :default, :development
4
+
5
+ # If you're using all parts of Rails:
6
+ Combustion.initialize! :all
7
+ # Or, load just what you need:
8
+ # Combustion.initialize! :active_record, :action_controller
9
+
10
+ require 'rspec/rails'
11
+ # If you're using Capybara:
12
+ # require 'capybara/rails'
13
+
14
+ RSpec.configure do |config|
15
+ config.use_transactional_fixtures = true
16
+ config.mock_with :rspec do |mocks|
17
+ mocks.allow_message_expectations_on_nil = true
18
+ end
19
+ end
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+ require 'rake'
3
+
4
+ load 'tasks/crono_tasks.rake'
5
+ Rake::Task.define_task(:environment)
6
+
7
+ describe 'rake' do
8
+ describe 'crono:clean' do
9
+ it 'should clean unused tasks from DB' do
10
+ Crono::CronoJob.create!(job_id: 'used_job')
11
+ ENV['CRONOTAB'] = File.expand_path('../../assets/good_cronotab.rb', __FILE__)
12
+ Rake::Task['crono:clean'].invoke
13
+ expect(Crono::CronoJob.where(job_id: 'used_job')).not_to exist
14
+ end
15
+ end
16
+
17
+ describe 'crono:check' do
18
+ it 'should check cronotab syntax' do
19
+ ENV['CRONOTAB'] = File.expand_path('../../assets/bad_cronotab.rb', __FILE__)
20
+ expect { Rake::Task['crono:check'].invoke }.to raise_error
21
+ end
22
+ end
23
+ end
data/spec/web_spec.rb ADDED
@@ -0,0 +1,61 @@
1
+ require 'spec_helper'
2
+ require 'rack/test'
3
+ include Rack::Test::Methods
4
+
5
+ describe Crono::Engine do
6
+ let(:app) { Crono::Engine }
7
+
8
+ before do
9
+ Crono::CronoJob.destroy_all
10
+ @test_job_id = 'Perform TestJob every 5 seconds'
11
+ @test_job_log = 'All runs ok'
12
+ @test_job = Crono::CronoJob.create!(
13
+ job_id: @test_job_id,
14
+ log: @test_job_log
15
+ )
16
+ end
17
+
18
+ after { @test_job.destroy }
19
+
20
+ describe '/' do
21
+ it 'should show all jobs' do
22
+ get '/'
23
+ expect(last_response).to be_ok
24
+ expect(last_response.body).to include @test_job_id
25
+ end
26
+
27
+ it 'should show a error mark when a job is unhealthy' do
28
+ @test_job.update(healthy: false, last_performed_at: 10.minutes.ago)
29
+ get '/'
30
+ expect(last_response.body).to include 'Error'
31
+ end
32
+
33
+ it 'should show a success mark when a job is healthy' do
34
+ @test_job.update(healthy: true, last_performed_at: 10.minutes.ago)
35
+ get '/'
36
+ expect(last_response.body).to include 'Success'
37
+ end
38
+
39
+ it 'should show a pending mark when a job is pending' do
40
+ @test_job.update(healthy: nil)
41
+ get '/'
42
+ expect(last_response.body).to include 'Pending'
43
+ end
44
+ end
45
+
46
+ describe '/job/:id' do
47
+ it 'should show job log' do
48
+ get "/jobs/#{@test_job.id}"
49
+ expect(last_response).to be_ok
50
+ expect(last_response.body).to include @test_job_id
51
+ expect(last_response.body).to include @test_job_log
52
+ end
53
+
54
+ it 'should show a message about the unhealthy job' do
55
+ message = 'An error occurs during the last execution of this job'
56
+ @test_job.update(healthy: false)
57
+ get "/jobs/#{@test_job.id}"
58
+ expect(last_response.body).to include message
59
+ end
60
+ end
61
+ end