2 * Copyright 2014 Facebook, Inc.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 #include <gtest/gtest.h>
19 #include "folly/experimental/cron/CrontabSelector.h"
20 #include "folly/json.h"
22 using namespace folly::cron;
23 using namespace folly;
26 // Templates lead to poor error messages, so the preprocessor rules this land
28 #define check_parse_impl(Selector, json, expected_printable) { \
30 c.loadDynamic(parseJson(json)); \
31 EXPECT_EQ(expected_printable, c.getPrintable()); \
34 #define check_first_match_impl(Selector, Num, json, t, match, carry) { \
35 Num match_ = match; /* Make the input the right type */ \
37 c.loadDynamic(parseJson(json)); \
38 EXPECT_EQ(make_pair(match_, carry), c.findFirstMatch(t)); \
41 #define check_parse(json, expected_printable) \
42 check_parse_impl(ClownCrontabSelector, json, expected_printable)
44 #define check_first_match(json, t, match, carry) \
45 check_first_match_impl( \
46 ClownCrontabSelector, unsigned short, json, t, match, carry \
50 * There are 7 clowns in the world, numbered from 1 through 7, with the
53 class ClownCrontabSelector : public CrontabSelector<unsigned short> {
55 ClownCrontabSelector() : CrontabSelector(1, 7) {}
57 virtual const PrefixMap& getPrefixMap() const { return clownToInt_; }
59 const static PrefixMap clownToInt_;
61 const ClownCrontabSelector::PrefixMap ClownCrontabSelector::clownToInt_{
66 {"thu", 5}, // Thurmond
67 {"fri", 6}, // Friedrich
71 TEST(TestCrontabSelector, CheckParseRanges) {
72 check_parse("{}", "*");
73 check_parse("{\"interval\": 3}", "1-7:3");
74 check_parse("{\"start\": 2}", "2-7:1");
75 check_parse("{\"start\": \"Sunny\"}", "*");
76 check_parse("{\"end\": \"Thu\"}", "1-5:1");
77 check_parse("{\"start\": \"Tuelly\", \"end\": \"Wed\"}", "3-4:1");
78 check_parse("{\"end\": \"Fri\", \"interval\": 2}", "1-6:2");
81 TEST(TestCrontabSelector, CheckParseValues) {
82 check_parse("5", "5");
83 check_parse("\"Thu\"", "5");
84 check_parse("[\"Sat\", 1, \"Fri\"]", "1,6,7");
87 TEST(TestCrontabSelector, CheckMatchValues) {
88 for (int64_t i = 0; i < 10; ++i) { // A single value never changes
89 check_first_match("5", i, 5, i > 5);
92 // Now check a list of values
93 check_first_match("[2,4,5]", 0, 2, false);
94 check_first_match("[2,4,5]", 1, 2, false);
95 check_first_match("[2,4,5]", 2, 2, false);
96 check_first_match("[2,4,5]", 3, 4, false);
97 check_first_match("[2,4,5]", 4, 4, false);
98 check_first_match("[2,4,5]", 5, 5, false);
99 check_first_match("[2,4,5]", 6, 2, true);
100 check_first_match("[2,4,5]", 7, 2, true);
103 TEST(TestCrontabSelector, CheckMatchDefaultRange) {
104 check_first_match("{}", 0, 1, false);
105 check_first_match("[]", 1, 1, false);
106 check_first_match("[]", 6, 6, false);
107 check_first_match("{}", 7, 7, false);
108 check_first_match("{}", 8, 1, true);
109 check_first_match("[]", 10, 1, true);
112 TEST(TestCrontabSelector, CheckMatchNontrivialRange) {
113 string range = "{\"start\": 3, \"end\": 7, \"interval\": 2}";
114 check_first_match(range, 1, 3, false);
115 check_first_match(range, 2, 3, false);
116 check_first_match(range, 3, 3, false);
117 check_first_match(range, 4, 5, false);
118 check_first_match(range, 5, 5, false);
119 check_first_match(range, 6, 7, false);
120 check_first_match(range, 7, 7, false);
121 check_first_match(range, 8, 3, true);
124 TEST(TestCrontabSelector, CheckInit) {
125 ClownCrontabSelector s;
127 EXPECT_THROW(s.findFirstMatch(5), runtime_error); // Not initialized
129 // Initialized and functional
130 s.loadDynamic(dynamic::object());
131 EXPECT_EQ(make_pair((unsigned short)5, false), s.findFirstMatch(5));
133 // Cannot double-initialize
134 EXPECT_THROW(s.loadDynamic(dynamic::object()), runtime_error);
137 TEST(TestCrontabSelector, CheckParseErrors) {
138 // Out-of-range intervals
139 EXPECT_THROW(check_parse("{\"interval\": 0}", ""), runtime_error);
140 EXPECT_THROW(check_parse("{\"interval\": -1}", ""), runtime_error);
141 EXPECT_THROW(check_parse("{\"interval\": 7}", ""), runtime_error);
143 // Out-of-range start or end
144 EXPECT_THROW(check_parse("{\"start\": 0}", ""), runtime_error);
145 EXPECT_THROW(check_parse("{\"end\": 8}", ""), runtime_error);
147 // Problematic JSON inputs
148 EXPECT_THROW(check_parse("{\"bad_key\": 3}", ""), runtime_error);
149 EXPECT_THROW(check_parse("0.1", ""), runtime_error); // no floats
152 EXPECT_THROW(check_parse("\"Chicken\"", ""), runtime_error);
153 EXPECT_THROW(check_parse("\"Th\"", ""), runtime_error); // Need >= 3 chars
154 EXPECT_THROW(check_parse("\"S\"", ""), runtime_error);
155 EXPECT_THROW(check_parse("{\"start\": 0.3}", ""), runtime_error); // float