Skip to content

Commit c9bb8ac

Browse files
committed
esp_daylight: Add unit tests
1 parent 09d7cc1 commit c9bb8ac

File tree

10 files changed

+342
-1
lines changed

10 files changed

+342
-1
lines changed

.codespellrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
[codespell]
22
skip = build,COPYING.*,LICENSE,*.svg
3-
ignore-words-list = ser,DOUT,dout,ans,nd,Dettached
3+
ignore-words-list = ser,DOUT,dout,ans,nd,Dettached,IST
44
write-changes = true

.idf_build_apps.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ manifest_file = [
77
"bdc_motor/.build-test-rules.yml",
88
"ccomp_timer/.build-test-rules.yml",
99
"coremark/.build-test-rules.yml",
10+
"esp_daylight/.build-test-rules.yml",
1011
"esp_encrypted_img/.build-test-rules.yml",
1112
"esp_gcov/.build-test-rules.yml",
1213
"esp_jpeg/.build-test-rules.yml",

esp_daylight/.build-test-rules.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Build and test rules for esp_daylight component
2+
# This file can be empty - the component will be built with default rules
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
cmake_minimum_required(VERSION 3.16)
2+
3+
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
4+
project(esp_daylight_test)
5+
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
idf_component_register(SRCS "test_app_main.c"
2+
"test_esp_daylight.c"
3+
INCLUDE_DIRS "."
4+
PRIV_REQUIRES unity esp_daylight
5+
WHOLE_ARCHIVE)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
dependencies:
2+
esp_daylight:
3+
path: "../.."
4+
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include "unity.h"
8+
#include "unity_test_runner.h"
9+
#include "esp_heap_caps.h"
10+
#include "esp_newlib.h"
11+
#include "unity_test_utils_memory.h"
12+
13+
void setUp(void)
14+
{
15+
unity_utils_record_free_mem();
16+
}
17+
18+
void tearDown(void)
19+
{
20+
esp_reent_cleanup(); /* clean up some of the newlib's lazy allocations */
21+
unity_utils_evaluate_leaks_direct(50);
22+
}
23+
24+
void app_main(void)
25+
{
26+
printf("Running esp_daylight component tests\n");
27+
unity_run_menu();
28+
}
Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include <stdio.h>
8+
#include <math.h>
9+
#include "unity.h"
10+
#include "esp_log.h"
11+
#include "esp_daylight.h"
12+
13+
static const char *TAG = "esp_daylight_test";
14+
15+
/* Test tolerance for time calculations (in seconds) */
16+
#define TIME_TOLERANCE_SEC 120 /* 2 minutes tolerance for sunrise/sunset calculations */
17+
18+
/* Helper function to check if two timestamps are within tolerance */
19+
__attribute__((unused)) static bool time_within_tolerance(time_t actual, time_t expected, int tolerance_sec)
20+
{
21+
int diff = abs((int)(actual - expected));
22+
return diff <= tolerance_sec;
23+
}
24+
25+
TEST_CASE("Test basic sunrise/sunset calculation", "[esp_daylight]")
26+
{
27+
time_t sunrise_utc, sunset_utc;
28+
bool result;
29+
30+
/* Test for Pune, India on August 29, 2025 */
31+
result = esp_daylight_calc_sunrise_sunset_utc(
32+
2025, 8, 29, /* Date */
33+
18.5204, 73.8567, /* Pune coordinates */
34+
&sunrise_utc, &sunset_utc
35+
);
36+
37+
TEST_ASSERT_TRUE(result);
38+
TEST_ASSERT_NOT_EQUAL(0, sunrise_utc);
39+
TEST_ASSERT_NOT_EQUAL(0, sunset_utc);
40+
TEST_ASSERT_GREATER_THAN(sunrise_utc, sunset_utc);
41+
42+
/* Convert to readable format for logging */
43+
struct tm sunrise_tm_buf, sunset_tm_buf;
44+
struct tm *sunrise_tm = gmtime_r(&sunrise_utc, &sunrise_tm_buf);
45+
struct tm *sunset_tm = gmtime_r(&sunset_utc, &sunset_tm_buf);
46+
47+
ESP_LOGI(TAG, "Pune 2025-08-29: Sunrise %02d:%02d UTC, Sunset %02d:%02d UTC",
48+
sunrise_tm->tm_hour, sunrise_tm->tm_min,
49+
sunset_tm->tm_hour, sunset_tm->tm_min);
50+
51+
/* Sanity check: sunrise should be around 01:00 UTC (06:30 IST) */
52+
/* sunset should be around 13:00 UTC (18:30 IST) */
53+
TEST_ASSERT_TRUE(sunrise_tm->tm_hour >= 0 && sunrise_tm->tm_hour <= 3);
54+
TEST_ASSERT_TRUE(sunset_tm->tm_hour >= 12 && sunset_tm->tm_hour <= 15);
55+
}
56+
57+
TEST_CASE("Test location struct interface", "[esp_daylight]")
58+
{
59+
esp_daylight_location_t location = {
60+
.latitude = 40.7128,
61+
.longitude = -74.0060,
62+
.name = "New York"
63+
};
64+
65+
time_t sunrise_utc, sunset_utc;
66+
bool result;
67+
68+
result = esp_daylight_calc_sunrise_sunset_location(
69+
2025, 6, 21, /* Summer solstice */
70+
&location,
71+
&sunrise_utc, &sunset_utc
72+
);
73+
74+
TEST_ASSERT_TRUE(result);
75+
TEST_ASSERT_NOT_EQUAL(0, sunrise_utc);
76+
TEST_ASSERT_NOT_EQUAL(0, sunset_utc);
77+
TEST_ASSERT_GREATER_THAN(sunrise_utc, sunset_utc);
78+
79+
struct tm sunrise_tm_buf, sunset_tm_buf;
80+
struct tm *sunrise_tm = gmtime_r(&sunrise_utc, &sunrise_tm_buf);
81+
struct tm *sunset_tm = gmtime_r(&sunset_utc, &sunset_tm_buf);
82+
83+
ESP_LOGI(TAG, "New York 2025-06-21: Sunrise %02d:%02d UTC, Sunset %02d:%02d UTC",
84+
sunrise_tm->tm_hour, sunrise_tm->tm_min,
85+
sunset_tm->tm_hour, sunset_tm->tm_min);
86+
}
87+
88+
TEST_CASE("Test polar regions - midnight sun", "[esp_daylight]")
89+
{
90+
time_t sunrise_utc, sunset_utc;
91+
bool result;
92+
93+
/* Test Arctic location during summer (midnight sun) */
94+
result = esp_daylight_calc_sunrise_sunset_utc(
95+
2025, 6, 21, /* Summer solstice */
96+
80.0, 0.0, /* High Arctic latitude */
97+
&sunrise_utc, &sunset_utc
98+
);
99+
100+
/* Should return false for midnight sun conditions */
101+
TEST_ASSERT_FALSE(result);
102+
ESP_LOGI(TAG, "Arctic midnight sun test: correctly returned false");
103+
}
104+
105+
TEST_CASE("Test polar regions - polar night", "[esp_daylight]")
106+
{
107+
time_t sunrise_utc, sunset_utc;
108+
bool result;
109+
110+
/* Test Arctic location during winter (polar night) */
111+
result = esp_daylight_calc_sunrise_sunset_utc(
112+
2025, 12, 21, /* Winter solstice */
113+
80.0, 0.0, /* High Arctic latitude */
114+
&sunrise_utc, &sunset_utc
115+
);
116+
117+
/* Should return false for polar night conditions */
118+
TEST_ASSERT_FALSE(result);
119+
ESP_LOGI(TAG, "Arctic polar night test: correctly returned false");
120+
}
121+
122+
TEST_CASE("Test time offset functionality", "[esp_daylight]")
123+
{
124+
time_t base_time = 1640995200; /* 2022-01-01 00:00:00 UTC */
125+
time_t offset_time;
126+
127+
/* Test positive offset (30 minutes after) */
128+
offset_time = esp_daylight_apply_offset(base_time, 30);
129+
TEST_ASSERT_EQUAL(base_time + 1800, offset_time);
130+
131+
/* Test negative offset (45 minutes before) */
132+
offset_time = esp_daylight_apply_offset(base_time, -45);
133+
TEST_ASSERT_EQUAL(base_time - 2700, offset_time);
134+
135+
/* Test zero offset */
136+
offset_time = esp_daylight_apply_offset(base_time, 0);
137+
TEST_ASSERT_EQUAL(base_time, offset_time);
138+
139+
ESP_LOGI(TAG, "Time offset tests passed");
140+
}
141+
142+
TEST_CASE("Test input validation", "[esp_daylight]")
143+
{
144+
time_t sunrise_utc, sunset_utc;
145+
146+
/* Test invalid date */
147+
(void) esp_daylight_calc_sunrise_sunset_utc(
148+
2025, 13, 1, /* Invalid month */
149+
18.5204, 73.8567,
150+
&sunrise_utc, &sunset_utc
151+
);
152+
/* Implementation should handle this gracefully */
153+
154+
/* Test extreme latitudes */
155+
(void) esp_daylight_calc_sunrise_sunset_utc(
156+
2025, 6, 21,
157+
91.0, 0.0, /* Invalid latitude > 90 */
158+
&sunrise_utc, &sunset_utc
159+
);
160+
/* Should handle gracefully */
161+
162+
/* Test extreme longitudes */
163+
(void) esp_daylight_calc_sunrise_sunset_utc(
164+
2025, 6, 21,
165+
0.0, 181.0, /* Invalid longitude > 180 */
166+
&sunrise_utc, &sunset_utc
167+
);
168+
/* Should handle gracefully */
169+
170+
ESP_LOGI(TAG, "Input validation tests completed");
171+
}
172+
173+
TEST_CASE("Test known reference values", "[esp_daylight]")
174+
{
175+
time_t sunrise_utc, sunset_utc;
176+
bool result;
177+
178+
/* Test London on summer solstice 2025 */
179+
result = esp_daylight_calc_sunrise_sunset_utc(
180+
2025, 6, 21,
181+
51.5074, -0.1278, /* London coordinates */
182+
&sunrise_utc, &sunset_utc
183+
);
184+
185+
TEST_ASSERT_TRUE(result);
186+
187+
struct tm sunrise_tm_buf, sunset_tm_buf;
188+
struct tm *sunrise_tm = gmtime_r(&sunrise_utc, &sunrise_tm_buf);
189+
struct tm *sunset_tm = gmtime_r(&sunset_utc, &sunset_tm_buf);
190+
191+
ESP_LOGI(TAG, "London 2025-06-21: Sunrise %02d:%02d UTC, Sunset %02d:%02d UTC",
192+
sunrise_tm->tm_hour, sunrise_tm->tm_min,
193+
sunset_tm->tm_hour, sunset_tm->tm_min);
194+
195+
/* London summer solstice: sunrise around 04:43 UTC, sunset around 20:21 UTC */
196+
TEST_ASSERT_TRUE(sunrise_tm->tm_hour >= 3 && sunrise_tm->tm_hour <= 6);
197+
TEST_ASSERT_TRUE(sunset_tm->tm_hour >= 19 && sunset_tm->tm_hour <= 22);
198+
}
199+
200+
TEST_CASE("Test equatorial location", "[esp_daylight]")
201+
{
202+
time_t sunrise_utc, sunset_utc;
203+
bool result;
204+
205+
/* Test Singapore (near equator) */
206+
result = esp_daylight_calc_sunrise_sunset_utc(
207+
2025, 3, 21, /* Equinox */
208+
1.3521, 103.8198, /* Singapore coordinates */
209+
&sunrise_utc, &sunset_utc
210+
);
211+
212+
TEST_ASSERT_TRUE(result);
213+
214+
struct tm sunrise_tm_buf, sunset_tm_buf;
215+
struct tm *sunrise_tm = gmtime_r(&sunrise_utc, &sunrise_tm_buf);
216+
struct tm *sunset_tm = gmtime_r(&sunset_utc, &sunset_tm_buf);
217+
218+
ESP_LOGI(TAG, "Singapore 2025-03-21: Sunrise %02d:%02d UTC, Sunset %02d:%02d UTC",
219+
sunrise_tm->tm_hour, sunrise_tm->tm_min,
220+
sunset_tm->tm_hour, sunset_tm->tm_min);
221+
222+
/* Near equator, day length should be close to 12 hours */
223+
int day_length_minutes = (sunset_utc - sunrise_utc) / 60;
224+
TEST_ASSERT_INT_WITHIN(30, 12 * 60, day_length_minutes); /* Within 30 minutes of 12 hours */
225+
}
226+
227+
TEST_CASE("Test southern hemisphere", "[esp_daylight]")
228+
{
229+
time_t sunrise_utc, sunset_utc;
230+
bool result;
231+
232+
/* Test Sydney, Australia */
233+
result = esp_daylight_calc_sunrise_sunset_utc(
234+
2025, 12, 21, /* Summer solstice in southern hemisphere */
235+
-33.8688, 151.2093, /* Sydney coordinates */
236+
&sunrise_utc, &sunset_utc
237+
);
238+
239+
TEST_ASSERT_TRUE(result);
240+
241+
struct tm sunrise_tm_buf, sunset_tm_buf;
242+
struct tm *sunrise_tm = gmtime_r(&sunrise_utc, &sunrise_tm_buf);
243+
struct tm *sunset_tm = gmtime_r(&sunset_utc, &sunset_tm_buf);
244+
245+
ESP_LOGI(TAG, "Sydney 2025-12-21: Sunrise %02d:%02d UTC, Sunset %02d:%02d UTC",
246+
sunrise_tm->tm_hour, sunrise_tm->tm_min,
247+
sunset_tm->tm_hour, sunset_tm->tm_min);
248+
249+
/* Should have valid sunrise/sunset times */
250+
TEST_ASSERT_NOT_EQUAL(0, sunrise_utc);
251+
TEST_ASSERT_NOT_EQUAL(0, sunset_utc);
252+
TEST_ASSERT_GREATER_THAN(sunrise_utc, sunset_utc);
253+
}
254+
255+
TEST_CASE("Test NULL pointer handling", "[esp_daylight]")
256+
{
257+
time_t sunrise_utc, sunset_utc;
258+
bool result;
259+
260+
/* Test NULL location pointer */
261+
result = esp_daylight_calc_sunrise_sunset_location(
262+
2025, 6, 21,
263+
NULL,
264+
&sunrise_utc, &sunset_utc
265+
);
266+
TEST_ASSERT_FALSE(result);
267+
268+
/* Test NULL output pointers (should not crash) */
269+
(void) esp_daylight_calc_sunrise_sunset_utc(
270+
2025, 6, 21,
271+
0.0, 0.0,
272+
NULL, NULL
273+
);
274+
/* Should handle gracefully */
275+
276+
ESP_LOGI(TAG, "NULL pointer handling tests completed");
277+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#!/usr/bin/env python3
2+
# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
import pytest
6+
from pytest_embedded import Dut
7+
8+
9+
@pytest.mark.generic
10+
def test_esp_daylight(dut: Dut) -> None:
11+
"""
12+
Test esp_daylight component functionality
13+
"""
14+
dut.run_all_single_board_cases(timeout=60)
15+
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# This file was generated using idf.py save-defconfig. It can be edited manually.
2+
# Espressif IoT Development Framework (ESP-IDF) 5.4.0 Project Minimal Configuration
3+
#
4+
CONFIG_ESP_TASK_WDT_INIT=n

0 commit comments

Comments
 (0)