Skip to content

Commit d2d9f7c

Browse files
committed
Adds a time zone fuzz test
Based on https://github.com/google/oss-fuzz/blob/master/projects/cctz/fuzz_cctz.cc but uses the Google FuzzTest framework and runs directly in the CCTZ project using Bazel
1 parent d85cf7b commit d2d9f7c

File tree

8 files changed

+683
-496
lines changed

8 files changed

+683
-496
lines changed

.github/workflows/ci.yaml

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ jobs:
1919
- name: Tests
2020
run: >
2121
bazel test ...
22+
--build_tag_filters=-fuzztest
2223
--copt=-DGTEST_REMOVE_LEGACY_TEST_CASEAPI_=1
2324
--copt=-Werror
2425
--cxxopt=-Wall
@@ -43,7 +44,7 @@ jobs:
4344
--show_timestamps
4445
--test_env="TZDIR=${GITHUB_WORKSPACE}/testdata/zoneinfo"
4546
--test_output=errors
46-
--test_tag_filters=-benchmark
47+
--test_tag_filters=-benchmark,-fuzztest
4748
4849
Linux-Clang:
4950
runs-on: ubuntu-latest
@@ -99,6 +100,32 @@ jobs:
99100
--test_output=errors
100101
--test_tag_filters=-benchmark
101102
103+
Linux-FuzzTest:
104+
runs-on: ubuntu-latest
105+
steps:
106+
107+
- uses: actions/checkout@v4
108+
with:
109+
fetch-depth: 0
110+
111+
- name: Configure Fuzztest
112+
run: bazel run @fuzztest//bazel:setup_configs > fuzztest.bazelrc
113+
- name: Fuzz
114+
run: >
115+
bazel --bazelrc=fuzztest.bazelrc test ...
116+
--action_env=CC=clang
117+
--build_tag_filters=fuzztest
118+
--config=fuzztest
119+
--copt=-DGTEST_REMOVE_LEGACY_TEST_CASEAPI_=1
120+
--define=absl=1
121+
--features=external_include_paths
122+
--keep_going
123+
--show_timestamps
124+
--test_arg=--fuzz_for=30s
125+
--test_env="TZDIR=${GITHUB_WORKSPACE}/testdata/zoneinfo"
126+
--test_output=errors
127+
--test_tag_filters=fuzztest
128+
102129
macOS:
103130
runs-on: macos-latest
104131
steps:
@@ -110,13 +137,14 @@ jobs:
110137
- name: Tests
111138
run: >
112139
bazel test ...
140+
--build_tag_filters=-fuzztest
113141
--cxxopt="-std=c++17"
114142
--features=external_include_paths
115143
--keep_going
116144
--show_timestamps
117145
--test_env="TZDIR=${GITHUB_WORKSPACE}/testdata/zoneinfo"
118146
--test_output=errors
119-
--test_tag_filters=-benchmark
147+
--test_tag_filters=-benchmark,-fuzztest
120148
121149
Windows:
122150
runs-on: windows-latest
@@ -129,8 +157,9 @@ jobs:
129157
- name: Tests
130158
run: >
131159
bazel test ...
160+
--build_tag_filters=-fuzztest
132161
--keep_going
133162
--show_timestamps
134163
--test_env="TZDIR=$env:GITHUB_WORKSPACE\testdata\zoneinfo"
135164
--test_output=errors
136-
--test_tag_filters=-benchmark
165+
--test_tag_filters=-benchmark,-fuzztest

BUILD

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,12 @@ cc_library(
7575

7676
### tests
7777

78-
test_suite(
79-
name = "all_tests",
80-
visibility = ["//visibility:public"],
78+
cc_library(
79+
name = "time_zone_test_util",
80+
testonly = True,
81+
srcs = ["src/time_zone_test_util.cc"],
82+
hdrs = ["src/time_zone_test_util.h"],
83+
visibility = ["//visibility:private"],
8184
)
8285

8386
cc_test(
@@ -110,11 +113,34 @@ cc_test(
110113
deps = [
111114
":civil_time",
112115
":time_zone",
116+
":time_zone_test_util",
113117
"@googletest//:gtest",
114118
"@googletest//:gtest_main",
115119
],
116120
)
117121

122+
cc_test(
123+
name = "time_zone_fuzz_test",
124+
srcs = [
125+
"src/time_zone_fuzz_test.cc",
126+
"src/time_zone_if.h",
127+
"src/time_zone_impl.h",
128+
"src/time_zone_info.h",
129+
"src/tzfile.h",
130+
],
131+
tags = [
132+
"fuzztest",
133+
],
134+
deps = [
135+
":civil_time",
136+
":time_zone",
137+
":time_zone_test_util",
138+
"@fuzztest//fuzztest",
139+
"@fuzztest//fuzztest:fuzztest_gtest_main",
140+
"@googletest//:gtest",
141+
],
142+
)
143+
118144
### benchmarks
119145

120146
cc_test(

CMakeLists.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,11 @@ if (BUILD_EXAMPLES)
111111
endif()
112112

113113
if (BUILD_TESTING)
114+
add_library(time_zone_test_util
115+
"src/time_zone_test_util.cc"
116+
"src/time_zone_test_util.h"
117+
)
118+
114119
add_executable(civil_time_test src/civil_time_test.cc)
115120
cctz_target_set_cxx_standard(civil_time_test)
116121
target_include_directories(civil_time_test PRIVATE ${GTEST_INCLUDE_DIRS})
@@ -126,6 +131,7 @@ if (BUILD_TESTING)
126131
target_include_directories(time_zone_lookup_test PRIVATE ${GTEST_INCLUDE_DIRS})
127132
target_link_libraries(time_zone_lookup_test
128133
cctz::cctz
134+
time_zone_test_util
129135
${GTEST_BOTH_LIBRARIES}
130136
${CMAKE_THREAD_LIBS_INIT}
131137
)

MODULE.bazel

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ module(
2222

2323
# Only direct dependencies need to be listed below.
2424

25+
bazel_dep(name = "fuzztest",
26+
version = "20250728.0",
27+
dev_dependency = True)
28+
2529
bazel_dep(name = "google_benchmark",
2630
version = "1.9.4",
2731
dev_dependency = True)

src/time_zone_fuzz_test.cc

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// Copyright 2025 Google Inc. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include <cstdint>
16+
#include <cstdlib>
17+
#include <limits>
18+
#include <string>
19+
#include <vector>
20+
21+
#include "cctz/civil_time.h"
22+
#include "cctz/time_zone.h"
23+
#include "fuzztest/fuzztest.h"
24+
#include "gtest/gtest.h"
25+
#include "time_zone_impl.h"
26+
#include "time_zone_test_util.h"
27+
28+
namespace cctz {
29+
30+
namespace {
31+
32+
std::vector<std::string> TimeZoneSeeds() {
33+
std::vector<std::string> time_zone_seeds;
34+
for (const char* const* tzptr = kTimeZoneNames; *tzptr != nullptr; ++tzptr) {
35+
time_zone_seeds.push_back(*tzptr);
36+
}
37+
return time_zone_seeds;
38+
}
39+
40+
void Fuzz(const std::string& time_zone_name, const std::int64_t year,
41+
const std::int64_t month, const std::int64_t day,
42+
const std::int64_t hour, const std::int64_t minute,
43+
const std::int64_t second, const std::string& format_string,
44+
const std::string& cctz_parse_input) {
45+
cctz::time_zone time_zone;
46+
if (!cctz::load_time_zone(time_zone_name, &time_zone)) {
47+
return;
48+
}
49+
const cctz::civil_second cs(year, month, day, hour, minute, second);
50+
auto time_point = cctz::convert(cs, time_zone);
51+
cctz::format(format_string, time_point, time_zone);
52+
cctz::time_point<cctz::seconds> parsed_time_point;
53+
cctz::parse(format_string, cctz_parse_input, time_zone, &parsed_time_point);
54+
auto repeated_time_point = cctz::convert(cs, time_zone);
55+
EXPECT_EQ(time_point, repeated_time_point);
56+
}
57+
58+
FUZZ_TEST(TimeZoneFuzzTest, Fuzz)
59+
.WithDomains(fuzztest::Arbitrary<std::string>().WithSeeds(TimeZoneSeeds()),
60+
// We limit the search-space for years to avoid expected
61+
// undefined behavior that may occur when normalizing a civil
62+
// second outside the 64-bit representable year range.
63+
fuzztest::InRange(std::numeric_limits<std::int64_t>::min() / 2,
64+
std::numeric_limits<std::int64_t>::max() /
65+
2), // year
66+
fuzztest::Arbitrary<std::int64_t>(), // month
67+
fuzztest::Arbitrary<std::int64_t>(), // day
68+
fuzztest::Arbitrary<std::int64_t>(), // hour
69+
fuzztest::Arbitrary<std::int64_t>(), // minute
70+
fuzztest::Arbitrary<std::int64_t>(), // second
71+
fuzztest::Arbitrary<std::string>(), // format_string
72+
fuzztest::Arbitrary<std::string>()); // cctz_parse_input
73+
74+
} // namespace
75+
76+
} // namespace cctz

0 commit comments

Comments
 (0)