diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..cfa5adc --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "library/ccronexpr"] + path = library/ccronexpr + url = https://github.com/staticlibs/ccronexpr.git diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..ddebd4c --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,6 @@ +set(COMPONENT_ADD_INCLUDEDIRS "." "include" "library/ccronexpr" "library/jobs") +set(COMPONENT_SRCDIRS "." "library/ccronexpr" "library/jobs") + +register_component() + +target_compile_definitions(${COMPONENT_TARGET} PUBLIC -D CRON_USE_LOCAL_TIME) diff --git a/README.md b/README.md index d5bf134..681cc82 100644 --- a/README.md +++ b/README.md @@ -2,16 +2,21 @@ This is a cron-like clone for the esp-idf framework. It uses cron-like sytanx and time libraries included in newlib (esp-idf framework) for task scheduling. +## How to clone + +To clone this module you need to do it recursively. + +After you add the submodule you should run `git submodule update --init --recursive` + ## How to use -We tried to keep module functions interface at minimum there is a creator, a destroyer a cron module starter and a cron module stopper. The workflow would be to define at least one job and then start the module. Then create and destroy jobs as desired. Keep in mind that if there are no jobs to be scheduled the cron module will stop itself, this is by design as we don't want to waste cpu time. +We tried to keep functions modules at minimum there is a creator, a destroyer a cron module starter and a cron module stopper. The workflow would be to define at least one job and then start the module. Then create and destroy jobs as desired. Keep in mind that if there are no jobs to be scheduled the cron module will stop itself. -Please remember that this module relies heavilly on the time.h library. **Time has to be initialized before any job creation.** The library time.h can be set manually or with another component like sntp, but it must have started before to this module is in use. This component will not perform any checks to idetify if time has been set. +Please keep in mind that this module relies heavilly on the time.h library. **Time has to be initialized before any job creation.** The library time.h can be set manually or with another component like sntp, but it must have started before to this module usage. ### Create -Usage is pretty simple, we provided a component factory for cron-job creation. - +Usage is pretty simple, we provided a component factory for cron-job creation. Please note that `cron_job_create()` will call `cron_start()` if the module is not running ```C cron_job *cron_job_create(const char *schedule, cron_job_callback callback, void *data) @@ -73,7 +78,7 @@ int cron_stop(); ### Clearing all jobs a.k.a destroying all jobs -We defined a helper to stop all cron jobs, we think it might be useful in some situations +We defined a helper to stop all cron jobs, we think it might be useful in some situations. This function calls `cron_stop()` ```C int cron_job_clear_all(); diff --git a/component.mk b/component.mk index b7b42cc..d2683cd 100644 --- a/component.mk +++ b/component.mk @@ -1,4 +1,3 @@ -ESP_IDF=${IDF_PATH} -COMPONENT_ADD_INCLUDEDIRS := include ${ESP_IDF}/tools/unit-test-app/components/unity/include/ library/ccronexpr library/jobs . -COMPONENT_SRCDIRS := library/ccronexpr library/jobs test src . -CFLAGS += -D CRON_USE_LOCAL_TIME +COMPONENT_ADD_INCLUDEDIRS += library/ccronexpr library/jobs +COMPONENT_SRCDIRS += library/ccronexpr library/jobs +CPPFLAGS += -D CRON_USE_LOCAL_TIME diff --git a/cron.c b/cron.c index ee14259..d109ffd 100644 --- a/cron.c +++ b/cron.c @@ -1,4 +1,4 @@ -// Copyright 2018 Insite SAS +// Copyright 2018 Insite SAS // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -16,36 +16,70 @@ // // Author: David Mora Rodriguez dmorar (at) insite.com.co // + #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" -#include "cron.h" +#include "freertos/semphr.h" #include "jobs.h" - +#include "cron.h" static struct { unsigned char running; TaskHandle_t handle; time_t seconds_until_next_execution; - + SemaphoreHandle_t semaphore; } state = { .running = 0, .handle = NULL, - .seconds_until_next_execution = -1 - }; + .seconds_until_next_execution = -1, + .semaphore = NULL +}; + +static inline int cron_job_lock(void) +{ + if (NULL == state.semaphore) { + state.semaphore = xSemaphoreCreateMutex(); + } + + return ((NULL != state.semaphore) && (pdTRUE == xSemaphoreTake(state.semaphore, 100))); +} + +static inline void cron_job_unlock(void) +{ + if (NULL != state.semaphore) { + xSemaphoreGive(state.semaphore); + } +} + +void cron_job_init(){ + if(state.semaphore == NULL) state.semaphore = xSemaphoreCreateMutex(); +} -cron_job *cron_job_create(const char *schedule, cron_job_callback callback, void *data) +int cron_job_is_running(){ + return state.running; +} + +cron_job * cron_job_create(const char *schedule, cron_job_callback callback, void *data) { - cron_job_list_init();// CALL THIS ON ANY CREATE + if (cron_job_is_running()) + { + return NULL; + } + + cron_job_list_init(); // CALL THIS ON ANY CREATE cron_job *job = calloc(sizeof(cron_job), 1); if (job == NULL) goto end; job->callback = callback; job->data = data; job->id = -1; - job->load=NULL; - cron_job_load_expression(job, schedule); + job->load = NULL; + if(cron_job_load_expression(job, schedule) != 0) { + free(job); + return NULL; + } cron_job_schedule(job); goto end; @@ -53,82 +87,132 @@ cron_job *cron_job_create(const char *schedule, cron_job_callback callback, void return job; } -int cron_job_destroy(cron_job *job) + + +enum cron_job_errors cron_job_destroy(cron_job *job) { - int ret=0; - if (job == NULL) { - ret = -1; + int ret = Cron_ok; + if (cron_job_is_running()) + { + ret = Cron_not_stopped; + goto end; + } + if (job == NULL) + { + ret = Cron_bad_job; goto end; } ret = cron_job_unschedule(job); free(job); + job = NULL; + goto end; end: return ret; } -int cron_job_clear_all() +enum cron_job_errors cron_job_clear_all() { + int ret = Cron_ok; + if (cron_job_is_running()) + { + return Cron_not_stopped; + } // REFACTOR THIS! while (cron_job_list_first()) { - cron_job_destroy(cron_job_list_first()->job); + ret = cron_job_destroy(cron_job_list_first()->job); + if (ret != 0) + break; } cron_job_list_reset_id(); - return 0; + goto end; + end: + return ret; } -int cron_stop() +enum cron_job_errors cron_stop() { - if (state.running == 0) + if (!cron_job_is_running()) { - return -1; + return Cron_is_stopped; } - TaskHandle_t xHandle; - state.running = 0; - xHandle = state.handle; - state.handle = NULL; - if (xHandle != NULL) { - - vTaskDelete(xHandle); + vTaskDelay(20/portTICK_PERIOD_MS); + TaskHandle_t xHandle = NULL; + if (cron_job_lock()) + { + xHandle = state.handle; + state.handle = NULL; + cron_job_unlock(); + state.running = 0; + if (xHandle != NULL) + { + vTaskDelete(xHandle); + } + } else { + return Cron_no_sempahore; } - return 0; + return Cron_ok; } -int cron_start() +enum cron_job_errors cron_start() { - BaseType_t xReturned; - if (state.running == 1 || state.handle != NULL) + if (cron_job_is_running()) { - return -1; + return Cron_not_stopped; + } else if ( state.handle != NULL) { + return Cron_scheduler_task_handle_set_but_stopped; } - + // this vTaskDelay is used to wait for the cron + vTaskDelay(20/portTICK_PERIOD_MS); /* Create the task, storing the handle. */ - xReturned = xTaskCreatePinnedToCore( + BaseType_t xReturned = xTaskCreatePinnedToCore( cron_schedule_task, /* Function that implements the task. */ "cron_schedule_task", /* Text name for the task. */ 4096, /* Stack size in words, not bytes. */ (void *)0, /* Parameter passed into the task. */ tskIDLE_PRIORITY + 2, /* Priority at which the task is created. */ - NULL, /* NO NEED FOR THE HANDLE! I GOT IT IN STATE. */ + &state.handle, /* Save the handle */ tskNO_AFFINITY); /* NO SPECIFIC CORE */ if (xReturned != pdPASS) { - /* The task was created. Use the task's handle to delete the task. */ + return Cron_unable_to_create_secheduler_task; } state.running = 1; - return 0; + return Cron_ok; } +enum cron_job_errors cron_job_sort() +{ + if (cron_job_is_running()) + { + return Cron_not_stopped; + } + cron_job *job = NULL; + int count = cron_job_node_count(); + cron_job ** job_list = malloc(sizeof(cron_job*)*count); + for(int i = 0; ijob; + cron_job_list_remove(job->id); + job_list[i] = job; + } + + for(int i=0; iid >= 0) { @@ -160,31 +244,37 @@ int cron_job_unschedule(cron_job *job) return ret; } - -int cron_job_load_expression(cron_job *job, const char * schedule) +enum cron_job_errors cron_job_load_expression(cron_job *job, const char *schedule) { const char *error = NULL; - + if (schedule != NULL) { memset(&(job->expression), 0, sizeof(job->expression)); cron_parse_expr(schedule, &(job->expression), &error); + if(error != NULL){ + return Cron_parse_expresion_error; + } job->load = &(job->expression); + return Cron_ok; + } else { + return Cron_bad_schedule; } - return 0; + return Cron_fail; } -int cron_job_has_loaded(cron_job *job) +enum cron_job_errors cron_job_has_loaded(cron_job *job) { - if (&(job->expression) == job->load) - { - return 1; + if(job == NULL){ + return Cron_bad_job; } - else + if (&(job->expression) == job->load) { - return 0; + return Cron_ok; + } else { + return Cron_expresion_not_loaded; } - return 0; // WONT RUN + return Cron_fail; } time_t cron_job_seconds_until_next_execution() @@ -192,23 +282,22 @@ time_t cron_job_seconds_until_next_execution() return state.seconds_until_next_execution; } +// CRON TASKS - -// CRON TASKS - - -void cron_schedule_job_launcher(void * args){ - if (args==NULL) { +void cron_schedule_job_launcher(void *args) +{ + if (args == NULL) + { goto end; } - cron_job * job = (cron_job *) args; + cron_job *job = (cron_job *)args; job->callback(job); goto end; +end: - end: - vTaskDelete(NULL); - return; + vTaskDelete(NULL); + return;// WONT RUN } void cron_schedule_task(void *args) @@ -221,43 +310,64 @@ void cron_schedule_task(void *args) { if (strncmp(args, "R1", 2) == 0) // OK I ADMIT IT, ITS NOT THE MOST BEAUTIFUL CODE EVER, BUT I NEED IT TO BE TESTABLE... DON'T WANT TO GROW OLD WAITING FOR TIME TO PASS... :P r1 = 1; + } else { + cron_job_sort(); } - while (true) { - state.running = 1; time(&now); + if (cron_job_list_first() == NULL) + { + + break; + } + job = cron_job_list_first()->job; + if (job == NULL) { - break;// THIS IS IT!!! THIS WILL + break; // THIS IS IT!!! THIS WILL } if (now >= job->next_execution) { - /* Create the task, IT WILL KILL ITSELF AFTER THE JOB IS DONE. */ - xTaskCreatePinnedToCore( - cron_schedule_job_launcher, /* Function that implements the task. */ - "cron_schedule_job_launcher", /* Text name for the task. */ - 4096, /* Stack size in BYTES, not bytes. */ - (void *)job, /* Job is passed into the task. */ - tskIDLE_PRIORITY + 2, /* Priority at which the task is created. */ - (NULL), /* No need for the handle */ - tskNO_AFFINITY); /* No specific core */ - cron_job_list_remove(job->id); // There is mutex in there that can mess with our timing, but i am not sure if we should move this to the new task. - cron_job_schedule(job); // There is mutex in there that can mess with our timing, but i am not sure if we should move this to the new task. + if (job->callback != NULL) + { + /* Create the task, IT WILL KILL ITSELF AFTER THE JOB IS DONE. */ + xTaskCreatePinnedToCore( + cron_schedule_job_launcher, /* Function that implements the task. */ + "cron_schedule_job_launcher", /* Text name for the task. */ + (4096*2), /* Stack size in BYTES, not bytes. */ + (void *)job, /* Job is passed into the task. */ + tskIDLE_PRIORITY + 2, /* Priority at which the task is created. */ + (NULL), /* No need for the handle */ + tskNO_AFFINITY); /* No specific core */ + } + if (cron_job_lock()){ + cron_job_list_remove(job->id); // There is mutex in there that can mess with our timing, but i am not sure if we should move this to the new task. + cron_job_schedule(job); // There is mutex in there that can mess with our timing, but i am not sure if we should move this to the new task. + cron_job_unlock(); + } } else { state.seconds_until_next_execution = job->next_execution - now; vTaskDelay((state.seconds_until_next_execution * 1000) / portTICK_PERIOD_MS); } + if (r1 != 0) { - break; + return; } } - cron_stop(); + // r1 is the testing variable + + if (cron_job_lock()){ + state.running = 0; + state.handle = NULL; + cron_job_unlock(); // this give shall be placed before the vTaskDelete + } + if (r1!=1) { + vTaskDelete(NULL);// THIS MEANS THIS IS RUNNING IN A DIFFERENT TASK + } return; } - - diff --git a/include/cron.h b/include/cron.h index bc1e2d8..ec2790a 100644 --- a/include/cron.h +++ b/include/cron.h @@ -25,6 +25,25 @@ +/* +* ENUM WITH CRON ERRORS +*/ + +enum cron_job_errors { + Cron_bad_id=-1000, + Cron_parse_expresion_error, + Cron_bad_schedule, + Cron_error_in_load_expression, + Cron_expresion_not_loaded, + Cron_unable_to_create_secheduler_task, + Cron_no_sempahore, + Cron_bad_job, + Cron_scheduler_task_handle_set_but_stopped, + Cron_not_stopped, + Cron_is_stopped, + Cron_fail=-1, + Cron_ok=0 +}; /* * STRUCT INFORMATION: Holds the information needed to run the cron job. * @@ -63,10 +82,26 @@ struct cron_job_struct // FUNCTION POINTER TO CALLBACKS typedef void (*cron_job_callback)(cron_job *); +/* +* SUMARY: Allocates a cron job on the heap with supplied parameters, if cron is not running, it will call cron_start() +* +* PARAMS: CRON SYNTAX SCHEDULE, CALLBACK (JOB), DATA FOR THE CALLBACK +* +* RETURNS: heap allocated cron_job +*/ +void cron_job_init(); +/* +* SUMARY: Tells wether the cron scheduler is currently running +* +* PARAMS: none +* +* RETURNS: 1 on running, 0 otherwise +*/ +int cron_job_is_running(); /* -* SUMARY: Allocates a cron job on the heap with supplied parameters +* SUMARY: Allocates a cron job on the heap with supplied parameters, if cron is not running, it will call cron_start() * * PARAMS: CRON SYNTAX SCHEDULE, CALLBACK (JOB), DATA FOR THE CALLBACK * @@ -83,7 +118,7 @@ cron_job * cron_job_create(const char * schedule,cron_job_callback callback, voi * RETURNS: heap allocated cron_job */ -int cron_job_destroy(cron_job * job); +enum cron_job_errors cron_job_destroy(cron_job * job); /* @@ -91,26 +126,26 @@ int cron_job_destroy(cron_job * job); * * PARAMS: removes all cron_jobs (no deallocation is performed by the call, memory must be handled out of this module) * -* RETURNS: 0 on success +* RETURNS: cron_job_errors enum constant for error checking */ -int cron_job_clear_all(); +enum cron_job_errors cron_job_clear_all(); /* * SUMMARY: Starts the schedule module (creates a new task on the operating system) * -* RETURNS: 0 on success +* RETURNS: cron_job_errors enum constant for error checking */ -int cron_start(); +enum cron_job_errors cron_start(); /* * SUMMARY: Stops the schedule module (deletes the cron task on the operating system) * -* RETURNS: 0 on success +* RETURNS: cron_job_errors enum constant for error checking */ -int cron_stop(); +enum cron_job_errors cron_stop(); /* @@ -118,21 +153,21 @@ int cron_stop(); * * PARAMS: cron_job to be scheduled (no allocation is performed by the call, memory must be handled out of this module) * -* RETURNS: 0 on success +* RETURNS: cron_job_errors enum constant for error checking */ -int cron_job_schedule(cron_job * job); +enum cron_job_errors cron_job_schedule(cron_job * job); /* * SUMMARY: Removes a cron_job from the schedule, this is ID based * * PARAMS: cron_job to be removed (no deallocation is performed by the call, memory must be handled out of this module) * -* RETURNS: 0 on success, -1 on job NULL, -2 on JOB not loaded +* RETURNS: cron_job_errors enum constant for error checking */ -int cron_job_unschedule(cron_job * job); +enum cron_job_errors cron_job_unschedule(cron_job * job); @@ -160,24 +195,31 @@ void cron_schedule_task(void *args); * * RETURNS: NO RETURN */ -int cron_job_load_expression(cron_job *job, const char * schedule); +enum cron_job_errors cron_job_load_expression(cron_job *job, const char * schedule); /* * SUMARY: Checks if load expression has loaded * * PARAMS: Cron job structure * -* RETURNS: 1 if it has loaded, 0 on error +* RETURNS: cron_job_errors enum constant for error checking */ -int cron_job_has_loaded(cron_job *job); +enum cron_job_errors cron_job_has_loaded(cron_job *job); /* * SUMARY: Returns seconds until next execution * * PARAMS: NONE * -* RETURNS: seconds until next execution +* RETURNS: cron_job_errors enum constant for error checking */ time_t cron_job_seconds_until_next_execution(); - - +/* +* SUMARY: Sort the cron list +* +* PARAMS: NONE +* +* RETURNS: cron_job_errors enum constant for error checking +*/ +enum cron_job_errors cron_job_sort(); #endif + diff --git a/library/ccronexpr b/library/ccronexpr new file mode 160000 index 0000000..85bbf61 --- /dev/null +++ b/library/ccronexpr @@ -0,0 +1 @@ +Subproject commit 85bbf619d5e7f69b32f12d7506bc955d56ac325a diff --git a/library/ccronexpr/.gitignore b/library/ccronexpr/.gitignore deleted file mode 100644 index c9e21d9..0000000 --- a/library/ccronexpr/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/build/ -/nbproject -/Makefile diff --git a/library/ccronexpr/.travis.yml b/library/ccronexpr/.travis.yml deleted file mode 100644 index ab65352..0000000 --- a/library/ccronexpr/.travis.yml +++ /dev/null @@ -1,34 +0,0 @@ -# Copyright 2017, alex at staticlibs.net -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -language: cpp - -sudo: false - -os: - - linux - - osx - -compiler: - - gcc - - clang - -script: - - $CC ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c89 -DCRON_TEST_MALLOC -o a.out && ./a.out - - $CXX ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c++11 -DCRON_TEST_MALLOC -o a.out && ./a.out - - $CXX ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c++11 -DCRON_TEST_MALLOC -DCRON_COMPILE_AS_CXX -o a.out && ./a.out - -notifications: - email: - on_success: always diff --git a/library/ccronexpr/LICENSE.txt b/library/ccronexpr/LICENSE.txt deleted file mode 100644 index de99e06..0000000 --- a/library/ccronexpr/LICENSE.txt +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2015, staticlibs.net - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/library/ccronexpr/README.md b/library/ccronexpr/README.md deleted file mode 100644 index 92053e5..0000000 --- a/library/ccronexpr/README.md +++ /dev/null @@ -1,91 +0,0 @@ -Cron expression parsing in ANSI C -================================= - -[![travis](https://travis-ci.org/staticlibs/ccronexpr.svg?branch=master)](https://travis-ci.org/staticlibs/ccronexpr) -[![appveyor](https://ci.appveyor.com/api/projects/status/github/staticlibs/ccronexpr?svg=true)](https://ci.appveyor.com/project/staticlibs/ccronexpr) - -Given a cron expression and a date, you can get the next date which satisfies the cron expression. - -Supports cron expressions with `seconds` field. Based on implementation of [CronSequenceGenerator](https://github.com/spring-projects/spring-framework/blob/babbf6e8710ab937cd05ece20270f51490299270/spring-context/src/main/java/org/springframework/scheduling/support/CronSequenceGenerator.java) from Spring Framework. - -Compiles and should work on Linux (GCC/Clang), Mac OS (Clang), Windows (MSVC), Android NDK, iOS and possibly on other platforms with `time.h` support. - -Supports compilation in C (89) and in C++ modes. - -Usage example -------------- - - #include "ccronexpr.h" - - cron_expr expr; - const char* err = NULL; - memset(&expr, 0, sizeof(expr)); - cron_parse_expr("0 */2 1-4 * * *", &expr, &err); - if (err) ... /* invalid expression */ - time_t cur = time(NULL); - time_t next = cron_next(&expr, cur); - - -Compilation and tests run examples ----------------------------------- - - gcc ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c89 -DCRON_TEST_MALLOC -o a.out && ./a.out - g++ ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c++11 -DCRON_TEST_MALLOC -o a.out && ./a.out - g++ ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c++11 -DCRON_TEST_MALLOC -DCRON_COMPILE_AS_CXX -o a.out && ./a.out - - clang ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c89 -DCRON_TEST_MALLOC -o a.out && ./a.out - clang++ ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c++11 -DCRON_TEST_MALLOC -o a.out && ./a.out - clang++ ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c++11 -DCRON_TEST_MALLOC -DCRON_COMPILE_AS_CXX -o a.out && ./a.out - - cl ccronexpr.c ccronexpr_test.c /W4 /D_CRT_SECURE_NO_WARNINGS && ccronexpr.exe - -Examples of supported expressions ---------------------------------- - -Expression, input date, next date: - - "*/15 * 1-4 * * *", "2012-07-01_09:53:50", "2012-07-02_01:00:00" - "0 */2 1-4 * * *", "2012-07-01_09:00:00", "2012-07-02_01:00:00" - "0 0 7 ? * MON-FRI", "2009-09-26_00:42:55", "2009-09-28_07:00:00" - "0 30 23 30 1/3 ?", "2011-04-30_23:30:00", "2011-07-30_23:30:00" - -See more examples in [tests](https://github.com/staticlibs/ccronexpr/blob/a1343bc5a546b13430bd4ac72f3b047ac08f8192/ccronexpr_test.c#L251). - -Timezones ---------- - -This implementation does not support explicit timezones handling. By default all dates are -processed as UTC (GMT) dates without timezone infomation. - -To use local dates (current system timezone) instead of GMT compile with `-DCRON_USE_LOCAL_TIME`. - -License information -------------------- - -This project is released under the [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0). - -Changelog ---------- - -**2018-05-23** - - * merged [#8](https://github.com/staticlibs/ccronexpr/pull/8) - * merged [#9](https://github.com/staticlibs/ccronexpr/pull/9) - * minor cleanups - -**2018-01-27** - - * merged [#6](https://github.com/staticlibs/ccronexpr/pull/6) - * updated license file (to the one parse-able by github) - -**2017-09-24** - - * merged [#4](https://github.com/staticlibs/ccronexpr/pull/4) - -**2016-06-17** - - * use thread-safe versions of `gmtime` and `localtime` - -**2015-02-28** - - * initial public version diff --git a/library/ccronexpr/appveyor.yml b/library/ccronexpr/appveyor.yml deleted file mode 100644 index 379b32f..0000000 --- a/library/ccronexpr/appveyor.yml +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright 2017, alex at staticlibs.net -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -image: Visual Studio 2017 - -configuration: Release - -build: off - -build_script: - - call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars32.bat" - - cl ccronexpr.c ccronexpr_test.c /W4 /D_CRT_SECURE_NO_WARNINGS /Feccronexpr_32.exe - - call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat" - - cl ccronexpr.c ccronexpr_test.c /W4 /D_CRT_SECURE_NO_WARNINGS /Feccronexpr_64.exe - -test_script: - - ccronexpr_32.exe - - ccronexpr_64.exe diff --git a/library/ccronexpr/ccronexpr.c b/library/ccronexpr/ccronexpr.c deleted file mode 100644 index b42ec0c..0000000 --- a/library/ccronexpr/ccronexpr.c +++ /dev/null @@ -1,1212 +0,0 @@ -/* - * Copyright 2015, alex at staticlibs.net - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* - * File: ccronexpr.c - * Author: alex - * - * Created on February 24, 2015, 9:35 AM - */ - -#include -#include -#include -#include -#include -#include -#include - -#include "ccronexpr.h" - -#define CRON_MAX_SECONDS 60 -#define CRON_MAX_MINUTES 60 -#define CRON_MAX_HOURS 24 -#define CRON_MAX_DAYS_OF_WEEK 8 -#define CRON_MAX_DAYS_OF_MONTH 32 -#define CRON_MAX_MONTHS 12 -#define CRON_MAX_YEARS_DIFF 4 - -#define CRON_CF_SECOND 0 -#define CRON_CF_MINUTE 1 -#define CRON_CF_HOUR_OF_DAY 2 -#define CRON_CF_DAY_OF_WEEK 3 -#define CRON_CF_DAY_OF_MONTH 4 -#define CRON_CF_MONTH 5 -#define CRON_CF_YEAR 6 - -#define CRON_CF_ARR_LEN 7 - -#define CRON_INVALID_INSTANT ((time_t) -1) - -static const char* const DAYS_ARR[] = { "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" }; -#define CRON_DAYS_ARR_LEN 7 -static const char* const MONTHS_ARR[] = { "FOO", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" }; -#define CRON_MONTHS_ARR_LEN 13 - -#define CRON_MAX_STR_LEN_TO_SPLIT 256 -#define CRON_MAX_NUM_TO_SRING 1000000000 -/* computes number of digits in decimal number */ -#define CRON_NUM_OF_DIGITS(num) (abs(num) < 10 ? 1 : \ - (abs(num) < 100 ? 2 : \ - (abs(num) < 1000 ? 3 : \ - (abs(num) < 10000 ? 4 : \ - (abs(num) < 100000 ? 5 : \ - (abs(num) < 1000000 ? 6 : \ - (abs(num) < 10000000 ? 7 : \ - (abs(num) < 100000000 ? 8 : \ - (abs(num) < 1000000000 ? 9 : 10))))))))) - -#ifndef _WIN32 -struct tm *gmtime_r(const time_t *timep, struct tm *result); -#ifdef CRON_USE_LOCAL_TIME -struct tm *localtime_r(const time_t *timep, struct tm *result); -#endif /* CRON_USE_LOCAL_TIME */ -#endif /* _WIN32 */ - -#ifndef CRON_TEST_MALLOC -#define cron_malloc(x) malloc(x); -#define cron_free(x) free(x); -#else /* CRON_TEST_MALLOC */ -void* cron_malloc(size_t n); -void cron_free(void* p); -#endif /* CRON_TEST_MALLOC */ - -#ifdef __MINGW32__ -/* To avoid warning when building with mingw */ -time_t _mkgmtime(struct tm* tm); -#endif /* __MINGW32__ */ - -/* Defining 'cron_mktime' to use use UTC (default) or local time */ -#ifndef CRON_USE_LOCAL_TIME - -/* http://stackoverflow.com/a/22557778 */ -#ifdef _WIN32 -time_t cron_mktime(struct tm* tm) { - return _mkgmtime(tm); -} -#else /* !_WIN32 */ -#ifndef ANDROID -/* can be hidden in time.h */ -time_t timegm(struct tm* __tp); -#endif /* ANDROID */ -time_t cron_mktime(struct tm* tm) { -#ifndef ANDROID - return timegm(tm); -#else /* ANDROID */ - /* https://github.com/adobe/chromium/blob/cfe5bf0b51b1f6b9fe239c2a3c2f2364da9967d7/base/os_compat_android.cc#L20 */ - static const time_t kTimeMax = ~(1L << (sizeof (time_t) * CHAR_BIT - 1)); - static const time_t kTimeMin = (1L << (sizeof (time_t) * CHAR_BIT - 1)); - time64_t result = timegm64(tm); - if (result < kTimeMin || result > kTimeMax) return -1; - return result; -#endif /* ANDROID */ -} -#endif /* _WIN32 */ - -struct tm* cron_time(time_t* date, struct tm* out) { -#ifdef __MINGW32__ - (void)(out); /* To avoid unused warning */ - return gmtime(date); -#else /* !__MINGW32__ */ -#ifdef _WIN32 - errno_t err = gmtime_s(out, date); - return 0 == err ? out : NULL; -#else /* !_WIN32 */ - return gmtime_r(date, out); -#endif /* _WIN32 */ -#endif /* __MINGW32__ */ -} - -#else /* CRON_USE_LOCAL_TIME */ - -time_t cron_mktime(struct tm* tm) { - return mktime(tm); -} - -struct tm* cron_time(time_t* date, struct tm* out) { -#ifdef _WIN32 - errno_t err = localtime_s(out, date); - return 0 == err ? out : NULL; -#else /* _WIN32 */ - return localtime_r(date, out); -#endif /* _WIN32 */ -} - -#endif /* CRON_USE_LOCAL_TIME */ - -void cron_set_bit(uint8_t* rbyte, int idx) { - uint8_t j = (uint8_t) (idx / 8); - uint8_t k = (uint8_t) (idx % 8); - - rbyte[j] |= (1 << k); -} - -void cron_del_bit(uint8_t* rbyte, int idx) { - uint8_t j = (uint8_t) (idx / 8); - uint8_t k = (uint8_t) (idx % 8); - - rbyte[j] &= ~(1 << k); -} - -uint8_t cron_get_bit(uint8_t* rbyte, int idx) { - uint8_t j = (uint8_t) (idx / 8); - uint8_t k = (uint8_t) (idx % 8); - - if (rbyte[j] & (1 << k)) { - return 1; - } else { - return 0; - } -} - -static void free_splitted(char** splitted, size_t len) { - size_t i; - if (!splitted) return; - for (i = 0; i < len; i++) { - if (splitted[i]) { - cron_free(splitted[i]); - } - } - cron_free(splitted); -} - -static char* strdupl(const char* str, size_t len) { - if (!str) return NULL; - char* res = (char*) cron_malloc(len + 1); - if (!res) return NULL; - memset(res, 0, len + 1); - memcpy(res, str, len); - return res; -} - -static unsigned int next_set_bit(uint8_t* bits, unsigned int max, unsigned int from_index, int* notfound) { - unsigned int i; - if (!bits) { - *notfound = 1; - return 0; - } - for (i = from_index; i < max; i++) { - if (cron_get_bit(bits, i)) return i; - } - *notfound = 1; - return 0; -} - -static void push_to_fields_arr(int* arr, int fi) { - int i; - if (!arr || -1 == fi) { - return; - } - for (i = 0; i < CRON_CF_ARR_LEN; i++) { - if (arr[i] == fi) return; - } - for (i = 0; i < CRON_CF_ARR_LEN; i++) { - if (-1 == arr[i]) { - arr[i] = fi; - return; - } - } -} - -static int add_to_field(struct tm* calendar, int field, int val) { - if (!calendar || -1 == field) { - return 1; - } - switch (field) { - case CRON_CF_SECOND: - calendar->tm_sec = calendar->tm_sec + val; - break; - case CRON_CF_MINUTE: - calendar->tm_min = calendar->tm_min + val; - break; - case CRON_CF_HOUR_OF_DAY: - calendar->tm_hour = calendar->tm_hour + val; - break; - case CRON_CF_DAY_OF_WEEK: /* mkgmtime ignores this field */ - case CRON_CF_DAY_OF_MONTH: - calendar->tm_mday = calendar->tm_mday + val; - break; - case CRON_CF_MONTH: - calendar->tm_mon = calendar->tm_mon + val; - break; - case CRON_CF_YEAR: - calendar->tm_year = calendar->tm_year + val; - break; - default: - return 1; /* unknown field */ - } - time_t res = cron_mktime(calendar); - if (CRON_INVALID_INSTANT == res) { - return 1; - } - return 0; -} - -/** - * Reset the calendar setting all the fields provided to zero. - */ -static int reset_min(struct tm* calendar, int field) { - if (!calendar || -1 == field) { - return 1; - } - switch (field) { - case CRON_CF_SECOND: - calendar->tm_sec = 0; - break; - case CRON_CF_MINUTE: - calendar->tm_min = 0; - break; - case CRON_CF_HOUR_OF_DAY: - calendar->tm_hour = 0; - break; - case CRON_CF_DAY_OF_WEEK: - calendar->tm_wday = 0; - break; - case CRON_CF_DAY_OF_MONTH: - calendar->tm_mday = 1; - break; - case CRON_CF_MONTH: - calendar->tm_mon = 0; - break; - case CRON_CF_YEAR: - calendar->tm_year = 0; - break; - default: - return 1; /* unknown field */ - } - time_t res = cron_mktime(calendar); - if (CRON_INVALID_INSTANT == res) { - return 1; - } - return 0; -} - -static int reset_all_min(struct tm* calendar, int* fields) { - int i; - int res = 0; - if (!calendar || !fields) { - return 1; - } - for (i = 0; i < CRON_CF_ARR_LEN; i++) { - if (-1 != fields[i]) { - res = reset_min(calendar, fields[i]); - if (0 != res) return res; - } - } - return 0; -} - -static int set_field(struct tm* calendar, int field, int val) { - if (!calendar || -1 == field) { - return 1; - } - switch (field) { - case CRON_CF_SECOND: - calendar->tm_sec = val; - break; - case CRON_CF_MINUTE: - calendar->tm_min = val; - break; - case CRON_CF_HOUR_OF_DAY: - calendar->tm_hour = val; - break; - case CRON_CF_DAY_OF_WEEK: - calendar->tm_wday = val; - break; - case CRON_CF_DAY_OF_MONTH: - calendar->tm_mday = val; - break; - case CRON_CF_MONTH: - calendar->tm_mon = val; - break; - case CRON_CF_YEAR: - calendar->tm_year = val; - break; - default: - return 1; /* unknown field */ - } - time_t res = cron_mktime(calendar); - if (CRON_INVALID_INSTANT == res) { - return 1; - } - return 0; -} - -/** - * Search the bits provided for the next set bit after the value provided, - * and reset the calendar. - */ -static unsigned int find_next(uint8_t* bits, unsigned int max, unsigned int value, struct tm* calendar, unsigned int field, unsigned int nextField, int* lower_orders, int* res_out) { - int notfound = 0; - int err = 0; - unsigned int next_value = next_set_bit(bits, max, value, ¬found); - /* roll over if needed */ - if (notfound) { - err = add_to_field(calendar, nextField, 1); - if (err) goto return_error; - err = reset_min(calendar, field); - if (err) goto return_error; - notfound = 0; - next_value = next_set_bit(bits, max, 0, ¬found); - } - if (notfound || next_value != value) { - err = set_field(calendar, field, next_value); - if (err) goto return_error; - err = reset_all_min(calendar, lower_orders); - if (err) goto return_error; - } - return next_value; - - return_error: - *res_out = 1; - return 0; -} - -static unsigned int find_next_day(struct tm* calendar, uint8_t* days_of_month, unsigned int day_of_month, uint8_t* days_of_week, unsigned int day_of_week, int* resets, int* res_out) { - int err; - unsigned int count = 0; - unsigned int max = 366; - while ((!cron_get_bit(days_of_month, day_of_month) || !cron_get_bit(days_of_week, day_of_week)) && count++ < max) { - err = add_to_field(calendar, CRON_CF_DAY_OF_MONTH, 1); - - if (err) goto return_error; - day_of_month = calendar->tm_mday; - day_of_week = calendar->tm_wday; - reset_all_min(calendar, resets); - } - return day_of_month; - - return_error: - *res_out = 1; - return 0; -} - -static int do_next(cron_expr* expr, struct tm* calendar, unsigned int dot) { - int i; - int res = 0; - int* resets = NULL; - int* empty_list = NULL; - unsigned int second = 0; - unsigned int update_second = 0; - unsigned int minute = 0; - unsigned int update_minute = 0; - unsigned int hour = 0; - unsigned int update_hour = 0; - unsigned int day_of_week = 0; - unsigned int day_of_month = 0; - unsigned int update_day_of_month = 0; - unsigned int month = 0; - unsigned int update_month = 0; - - resets = (int*) cron_malloc(CRON_CF_ARR_LEN * sizeof(int)); - if (!resets) goto return_result; - empty_list = (int*) cron_malloc(CRON_CF_ARR_LEN * sizeof(int)); - if (!empty_list) goto return_result; - for (i = 0; i < CRON_CF_ARR_LEN; i++) { - resets[i] = -1; - empty_list[i] = -1; - } - - second = calendar->tm_sec; - update_second = find_next(expr->seconds, CRON_MAX_SECONDS, second, calendar, CRON_CF_SECOND, CRON_CF_MINUTE, empty_list, &res); - if (0 != res) goto return_result; - if (second == update_second) { - push_to_fields_arr(resets, CRON_CF_SECOND); - } - - minute = calendar->tm_min; - update_minute = find_next(expr->minutes, CRON_MAX_MINUTES, minute, calendar, CRON_CF_MINUTE, CRON_CF_HOUR_OF_DAY, resets, &res); - if (0 != res) goto return_result; - if (minute == update_minute) { - push_to_fields_arr(resets, CRON_CF_MINUTE); - } else { - res = do_next(expr, calendar, dot); - if (0 != res) goto return_result; - } - - hour = calendar->tm_hour; - update_hour = find_next(expr->hours, CRON_MAX_HOURS, hour, calendar, CRON_CF_HOUR_OF_DAY, CRON_CF_DAY_OF_WEEK, resets, &res); - if (0 != res) goto return_result; - if (hour == update_hour) { - push_to_fields_arr(resets, CRON_CF_HOUR_OF_DAY); - } else { - res = do_next(expr, calendar, dot); - if (0 != res) goto return_result; - } - - day_of_week = calendar->tm_wday; - day_of_month = calendar->tm_mday; - update_day_of_month = find_next_day(calendar, expr->days_of_month, day_of_month, expr->days_of_week, day_of_week, resets, &res); - if (0 != res) goto return_result; - if (day_of_month == update_day_of_month) { - push_to_fields_arr(resets, CRON_CF_DAY_OF_MONTH); - } else { - res = do_next(expr, calendar, dot); - if (0 != res) goto return_result; - } - - month = calendar->tm_mon; /*day already adds one if no day in same month is found*/ - update_month = find_next(expr->months, CRON_MAX_MONTHS, month, calendar, CRON_CF_MONTH, CRON_CF_YEAR, resets, &res); - if (0 != res) goto return_result; - if (month != update_month) { - if (calendar->tm_year - dot > 4) { - res = -1; - goto return_result; - } - res = do_next(expr, calendar, dot); - if (0 != res) goto return_result; - } - goto return_result; - - return_result: - if (!resets || !empty_list) { - res = -1; - } - if (resets) { - cron_free(resets); - } - if (empty_list) { - cron_free(empty_list); - } - return res; -} - -static int to_upper(char* str) { - if (!str) return 1; - int i; - for (i = 0; '\0' != str[i]; i++) { - int c = (int)str[i]; - str[i] = (char) toupper(c); - } - return 0; -} - -static char* to_string(int num) { - if (abs(num) >= CRON_MAX_NUM_TO_SRING) return NULL; - char* str = (char*) cron_malloc(CRON_NUM_OF_DIGITS(num) + 1); - if (!str) return NULL; - int res = sprintf(str, "%d", num); - if (res < 0) return NULL; - return str; -} - -static char* str_replace(char *orig, const char *rep, const char *with) { - char *result; /* the return string */ - char *ins; /* the next insert point */ - char *tmp; /* varies */ - size_t len_rep; /* length of rep */ - size_t len_with; /* length of with */ - size_t len_front; /* distance between rep and end of last rep */ - int count; /* number of replacements */ - if (!orig) return NULL; - if (!rep) rep = ""; - if (!with) with = ""; - len_rep = strlen(rep); - len_with = strlen(with); - - ins = orig; - for (count = 0; NULL != (tmp = strstr(ins, rep)); ++count) { - ins = tmp + len_rep; - } - - /* first time through the loop, all the variable are set correctly - from here on, - tmp points to the end of the result string - ins points to the next occurrence of rep in orig - orig points to the remainder of orig after "end of rep" - */ - tmp = result = (char*) cron_malloc(strlen(orig) + (len_with - len_rep) * count + 1); - if (!result) return NULL; - - while (count--) { - ins = strstr(orig, rep); - len_front = ins - orig; - tmp = strncpy(tmp, orig, len_front) + len_front; - tmp = strcpy(tmp, with) + len_with; - orig += len_front + len_rep; /* move to next "end of rep" */ - } - strcpy(tmp, orig); - return result; -} - -static unsigned int parse_uint(const char* str, int* errcode) { - char* endptr; - errno = 0; - long int l = strtol(str, &endptr, 0); - if (errno == ERANGE || *endptr != '\0' || l < 0 || l > INT_MAX) { - *errcode = 1; - return 0; - } else { - *errcode = 0; - return (unsigned int) l; - } -} - -static char** split_str(const char* str, char del, size_t* len_out) { - size_t i; - size_t stlen = 0; - size_t len = 0; - int accum = 0; - char* buf = NULL; - char** res = NULL; - size_t bi = 0; - size_t ri = 0; - char* tmp; - - if (!str) goto return_error; - for (i = 0; '\0' != str[i]; i++) { - stlen += 1; - if (stlen >= CRON_MAX_STR_LEN_TO_SPLIT) goto return_error; - } - - for (i = 0; i < stlen; i++) { - int c = str[i]; - if (del == str[i]) { - if (accum > 0) { - len += 1; - accum = 0; - } - } else if (!isspace(c)) { - accum += 1; - } - } - /* tail */ - if (accum > 0) { - len += 1; - } - if (0 == len) return NULL; - - buf = (char*) cron_malloc(stlen + 1); - if (!buf) goto return_error; - memset(buf, 0, stlen + 1); - res = (char**) cron_malloc(len * sizeof(char*)); - if (!res) goto return_error; - memset(res, 0, len * sizeof(char*)); - - for (i = 0; i < stlen; i++) { - int c = str[i]; - if (del == str[i]) { - if (bi > 0) { - tmp = strdupl(buf, bi); - if (!tmp) goto return_error; - res[ri++] = tmp; - memset(buf, 0, stlen + 1); - bi = 0; - } - } else if (!isspace(c)) { - buf[bi++] = str[i]; - } - } - /* tail */ - if (bi > 0) { - tmp = strdupl(buf, bi); - if (!tmp) goto return_error; - res[ri++] = tmp; - } - cron_free(buf); - *len_out = len; - return res; - - return_error: - if (buf) { - cron_free(buf); - } - free_splitted(res, len); - *len_out = 0; - return NULL; -} - -static char* replace_ordinals(char* value, const char* const * arr, size_t arr_len) { - size_t i; - char* cur = value; - char* res = NULL; - int first = 1; - for (i = 0; i < arr_len; i++) { - char* strnum = to_string((int) i); - if (!strnum) { - if (!first) { - cron_free(cur); - } - return NULL; - } - res = str_replace(cur, arr[i], strnum); - cron_free(strnum); - if (!first) { - cron_free(cur); - } - if (!res) { - return NULL; - } - cur = res; - if (first) { - first = 0; - } - } - return res; -} - -static int has_char(char* str, char ch) { - size_t i; - size_t len = 0; - if (!str) return 0; - len = strlen(str); - for (i = 0; i < len; i++) { - if (str[i] == ch) return 1; - } - return 0; -} - -static unsigned int* get_range(char* field, unsigned int min, unsigned int max, const char** error) { - - char** parts = NULL; - size_t len = 0; - unsigned int* res = (unsigned int*) cron_malloc(2 * sizeof(unsigned int)); - if (!res) goto return_error; - - res[0] = 0; - res[1] = 0; - if (1 == strlen(field) && '*' == field[0]) { - res[0] = min; - res[1] = max - 1; - } else if (!has_char(field, '-')) { - int err = 0; - unsigned int val = parse_uint(field, &err); - if (err) { - *error = "Unsigned integer parse error 1"; - goto return_error; - } - - res[0] = val; - res[1] = val; - } else { - parts = split_str(field, '-', &len); - if (2 != len) { - *error = "Specified range requires two fields"; - goto return_error; - } - int err = 0; - res[0] = parse_uint(parts[0], &err); - if (err) { - *error = "Unsigned integer parse error 2"; - goto return_error; - } - res[1] = parse_uint(parts[1], &err); - if (err) { - *error = "Unsigned integer parse error 3"; - goto return_error; - } - } - if (res[0] >= max || res[1] >= max) { - *error = "Specified range exceeds maximum"; - goto return_error; - } - if (res[0] < min || res[1] < min) { - *error = "Specified range is less than minimum"; - goto return_error; - } - if (res[0] > res[1]) { - *error = "Specified range start exceeds range end"; - goto return_error; - } - - free_splitted(parts, len); - *error = NULL; - return res; - - return_error: - free_splitted(parts, len); - if (res) { - cron_free(res); - } - - return NULL; -} - -static void set_number_hits(const char* value, uint8_t* target, unsigned int min, unsigned int max, const char** error) { - size_t i; - unsigned int i1; - size_t len = 0; - - char** fields = split_str(value, ',', &len); - if (!fields) { - *error = "Comma split error"; - goto return_result; - } - - for (i = 0; i < len; i++) { - if (!has_char(fields[i], '/')) { - /* Not an incrementer so it must be a range (possibly empty) */ - - unsigned int* range = get_range(fields[i], min, max, error); - - if (*error) { - if (range) { - cron_free(range); - } - goto return_result; - - } - - for (i1 = range[0]; i1 <= range[1]; i1++) { - cron_set_bit(target, i1); - - } - cron_free(range); - - } else { - size_t len2 = 0; - char** split = split_str(fields[i], '/', &len2); - if (2 != len2) { - *error = "Incrementer must have two fields"; - free_splitted(split, len2); - goto return_result; - } - unsigned int* range = get_range(split[0], min, max, error); - if (*error) { - if (range) { - cron_free(range); - } - free_splitted(split, len2); - goto return_result; - } - if (!has_char(split[0], '-')) { - range[1] = max - 1; - } - int err = 0; - unsigned int delta = parse_uint(split[1], &err); - if (err) { - *error = "Unsigned integer parse error 4"; - cron_free(range); - free_splitted(split, len2); - goto return_result; - } - if (0 == delta) { - *error = "Incrementer may not be zero"; - cron_free(range); - free_splitted(split, len2); - goto return_result; - } - for (i1 = range[0]; i1 <= range[1]; i1 += delta) { - cron_set_bit(target, i1); - } - free_splitted(split, len2); - cron_free(range); - - } - } - goto return_result; - - return_result: - free_splitted(fields, len); - -} - -static void set_months(char* value, uint8_t* targ, const char** error) { - int err; - unsigned int i; - unsigned int max = 12; - - char* replaced = NULL; - - err = to_upper(value); - if (err) return; - replaced = replace_ordinals(value, MONTHS_ARR, CRON_MONTHS_ARR_LEN); - if (!replaced) return; - - set_number_hits(replaced, targ, 1, max + 1, error); - cron_free(replaced); - - /* ... and then rotate it to the front of the months */ - for (i = 1; i <= max; i++) { - if (cron_get_bit(targ, i)) { - cron_set_bit(targ, i - 1); - cron_del_bit(targ, i); - } - } -} - -static void set_days(char* field, uint8_t* targ, int max, const char** error) { - if (1 == strlen(field) && '?' == field[0]) { - field[0] = '*'; - } - set_number_hits(field, targ, 0, max, error); -} - -static void set_days_of_month(char* field, uint8_t* targ, const char** error) { - /* Days of month start with 1 (in Cron and Calendar) so add one */ - if (1 == strlen(field) && '?' == field[0]) { - field[0] = '*'; - } - set_number_hits(field, targ, 1, CRON_MAX_DAYS_OF_MONTH, error); -} - -void cron_parse_expr(const char* expression, cron_expr* target, const char** error) { - const char* err_local; - size_t len = 0; - char** fields = NULL; - char* days_replaced = NULL; - if (!error) { - error = &err_local; - } - *error = NULL; - if (!expression) { - *error = "Invalid NULL expression"; - goto return_res; - } - - fields = split_str(expression, ' ', &len); - if (len != 6) { - *error = "Invalid number of fields, expression must consist of 6 fields"; - goto return_res; - } - set_number_hits(fields[0], target->seconds, 0, 60, error); - if (*error) goto return_res; - set_number_hits(fields[1], target->minutes, 0, 60, error); - if (*error) goto return_res; - set_number_hits(fields[2], target->hours, 0, 24, error); - if (*error) goto return_res; - to_upper(fields[5]); - days_replaced = replace_ordinals(fields[5], DAYS_ARR, CRON_DAYS_ARR_LEN); - set_days(days_replaced, target->days_of_week, 8, error); - cron_free(days_replaced); - if (*error) goto return_res; - if (cron_get_bit(target->days_of_week, 7)) { - /* Sunday can be represented as 0 or 7*/ - cron_set_bit(target->days_of_week, 0); - cron_del_bit(target->days_of_week, 7); - } - set_days_of_month(fields[3], target->days_of_month, error); - if (*error) goto return_res; - set_months(fields[4], target->months, error); - if (*error) goto return_res; - - goto return_res; - - return_res: - free_splitted(fields, len); -} - -time_t cron_next(cron_expr* expr, time_t date) { - /* - The plan: - - 1 Round up to the next whole second - - 2 If seconds match move on, otherwise find the next match: - 2.1 If next match is in the next minute then roll forwards - - 3 If minute matches move on, otherwise find the next match - 3.1 If next match is in the next hour then roll forwards - 3.2 Reset the seconds and go to 2 - - 4 If hour matches move on, otherwise find the next match - 4.1 If next match is in the next day then roll forwards, - 4.2 Reset the minutes and seconds and go to 2 - - ... - */ - if (!expr) return CRON_INVALID_INSTANT; - struct tm calval; - memset(&calval, 0, sizeof(struct tm)); - struct tm* calendar = cron_time(&date, &calval); - if (!calendar) return CRON_INVALID_INSTANT; - time_t original = cron_mktime(calendar); - if (CRON_INVALID_INSTANT == original) return CRON_INVALID_INSTANT; - - int res = do_next(expr, calendar, calendar->tm_year); - if (0 != res) return CRON_INVALID_INSTANT; - - time_t calculated = cron_mktime(calendar); - if (CRON_INVALID_INSTANT == calculated) return CRON_INVALID_INSTANT; - if (calculated == original) { - /* We arrived at the original timestamp - round up to the next whole second and try again... */ - res = add_to_field(calendar, CRON_CF_SECOND, 1); - if (0 != res) return CRON_INVALID_INSTANT; - res = do_next(expr, calendar, calendar->tm_year); - if (0 != res) return CRON_INVALID_INSTANT; - } - - return cron_mktime(calendar); -} - - -/* https://github.com/staticlibs/ccronexpr/pull/8 */ - -static unsigned int prev_set_bit(uint8_t* bits, int from_index, int to_index, int* notfound) { - int i; - if (!bits) { - *notfound = 1; - return 0; - } - for (i = from_index; i >= to_index; i--) { - if (cron_get_bit(bits, i)) return i; - } - *notfound = 1; - return 0; -} - -static int last_day_of_month(int month, int year) { - struct tm cal; - time_t t; - memset(&cal,0,sizeof(cal)); - cal.tm_sec=0; - cal.tm_min=0; - cal.tm_hour=0; - cal.tm_mon = month+1; - cal.tm_mday = 0; - cal.tm_year=year; - t=mktime(&cal); - return gmtime(&t)->tm_mday; -} - -/** - * Reset the calendar setting all the fields provided to zero. - */ -static int reset_max(struct tm* calendar, int field) { - if (!calendar || -1 == field) { - return 1; - } - switch (field) { - case CRON_CF_SECOND: - calendar->tm_sec = 59; - break; - case CRON_CF_MINUTE: - calendar->tm_min = 59; - break; - case CRON_CF_HOUR_OF_DAY: - calendar->tm_hour = 23; - break; - case CRON_CF_DAY_OF_WEEK: - calendar->tm_wday = 6; - break; - case CRON_CF_DAY_OF_MONTH: - calendar->tm_mday = last_day_of_month(calendar->tm_mon, calendar->tm_year); - break; - case CRON_CF_MONTH: - calendar->tm_mon = 11; - break; - case CRON_CF_YEAR: - /* I don't think this is supposed to happen ... */ - fprintf(stderr, "reset CRON_CF_YEAR\n"); - break; - default: - return 1; /* unknown field */ - } - time_t res = cron_mktime(calendar); - if (CRON_INVALID_INSTANT == res) { - return 1; - } - return 0; -} - -static int reset_all_max(struct tm* calendar, int* fields) { - int i; - int res = 0; - if (!calendar || !fields) { - return 1; - } - for (i = 0; i < CRON_CF_ARR_LEN; i++) { - if (-1 != fields[i]) { - res = reset_max(calendar, fields[i]); - if (0 != res) return res; - } - } - return 0; -} - -/** - * Search the bits provided for the next set bit after the value provided, - * and reset the calendar. - */ -static unsigned int find_prev(uint8_t* bits, unsigned int max, unsigned int value, struct tm* calendar, unsigned int field, unsigned int nextField, int* lower_orders, int* res_out) { - int notfound = 0; - int err = 0; - unsigned int next_value = prev_set_bit(bits, value, 0, ¬found); - /* roll under if needed */ - if (notfound) { - err = add_to_field(calendar, nextField, -1); - if (err) goto return_error; - err = reset_max(calendar, field); - if (err) goto return_error; - notfound = 0; - next_value = prev_set_bit(bits, max - 1, value, ¬found); - } - if (notfound || next_value != value) { - err = set_field(calendar, field, next_value); - if (err) goto return_error; - err = reset_all_max(calendar, lower_orders); - if (err) goto return_error; - } - return next_value; - - return_error: - *res_out = 1; - return 0; -} - -static unsigned int find_prev_day(struct tm* calendar, uint8_t* days_of_month, unsigned int day_of_month, uint8_t* days_of_week, unsigned int day_of_week, int* resets, int* res_out) { - int err; - unsigned int count = 0; - unsigned int max = 366; - while ((!cron_get_bit(days_of_month, day_of_month) || !cron_get_bit(days_of_week, day_of_week)) && count++ < max) { - err = add_to_field(calendar, CRON_CF_DAY_OF_MONTH, -1); - - if (err) goto return_error; - day_of_month = calendar->tm_mday; - day_of_week = calendar->tm_wday; - reset_all_max(calendar, resets); - } - return day_of_month; - - return_error: - *res_out = 1; - return 0; -} - -static int do_prev(cron_expr* expr, struct tm* calendar, unsigned int dot) { - int i; - int res = 0; - int* resets = NULL; - int* empty_list = NULL; - unsigned int second = 0; - unsigned int update_second = 0; - unsigned int minute = 0; - unsigned int update_minute = 0; - unsigned int hour = 0; - unsigned int update_hour = 0; - unsigned int day_of_week = 0; - unsigned int day_of_month = 0; - unsigned int update_day_of_month = 0; - unsigned int month = 0; - unsigned int update_month = 0; - - resets = (int*) cron_malloc(CRON_CF_ARR_LEN * sizeof(int)); - if (!resets) goto return_result; - empty_list = (int*) cron_malloc(CRON_CF_ARR_LEN * sizeof(int)); - if (!empty_list) goto return_result; - for (i = 0; i < CRON_CF_ARR_LEN; i++) { - resets[i] = -1; - empty_list[i] = -1; - } - - second = calendar->tm_sec; - update_second = find_prev(expr->seconds, CRON_MAX_SECONDS, second, calendar, CRON_CF_SECOND, CRON_CF_MINUTE, empty_list, &res); - if (0 != res) goto return_result; - if (second == update_second) { - push_to_fields_arr(resets, CRON_CF_SECOND); - } - - minute = calendar->tm_min; - update_minute = find_prev(expr->minutes, CRON_MAX_MINUTES, minute, calendar, CRON_CF_MINUTE, CRON_CF_HOUR_OF_DAY, resets, &res); - if (0 != res) goto return_result; - if (minute == update_minute) { - push_to_fields_arr(resets, CRON_CF_MINUTE); - } else { - res = do_prev(expr, calendar, dot); - if (0 != res) goto return_result; - } - - hour = calendar->tm_hour; - update_hour = find_prev(expr->hours, CRON_MAX_HOURS, hour, calendar, CRON_CF_HOUR_OF_DAY, CRON_CF_DAY_OF_WEEK, resets, &res); - if (0 != res) goto return_result; - if (hour == update_hour) { - push_to_fields_arr(resets, CRON_CF_HOUR_OF_DAY); - } else { - res = do_prev(expr, calendar, dot); - if (0 != res) goto return_result; - } - - day_of_week = calendar->tm_wday; - day_of_month = calendar->tm_mday; - update_day_of_month = find_prev_day(calendar, expr->days_of_month, day_of_month, expr->days_of_week, day_of_week, resets, &res); - if (0 != res) goto return_result; - if (day_of_month == update_day_of_month) { - push_to_fields_arr(resets, CRON_CF_DAY_OF_MONTH); - } else { - res = do_prev(expr, calendar, dot); - if (0 != res) goto return_result; - } - - month = calendar->tm_mon; /*day already adds one if no day in same month is found*/ - update_month = find_prev(expr->months, CRON_MAX_MONTHS, month, calendar, CRON_CF_MONTH, CRON_CF_YEAR, resets, &res); - if (0 != res) goto return_result; - if (month != update_month) { - if (dot - calendar->tm_year > CRON_MAX_YEARS_DIFF) { - res = -1; - goto return_result; - } - res = do_prev(expr, calendar, dot); - if (0 != res) goto return_result; - } - goto return_result; - - return_result: - if (!resets || !empty_list) { - res = -1; - } - if (resets) { - cron_free(resets); - } - if (empty_list) { - cron_free(empty_list); - } - return res; -} - -time_t cron_prev(cron_expr* expr, time_t date) { - /* - The plan: - - 1 Round down to a whole second - - 2 If seconds match move on, otherwise find the next match: - 2.1 If next match is in the next minute then roll forwards - - 3 If minute matches move on, otherwise find the next match - 3.1 If next match is in the next hour then roll forwards - 3.2 Reset the seconds and go to 2 - - 4 If hour matches move on, otherwise find the next match - 4.1 If next match is in the next day then roll forwards, - 4.2 Reset the minutes and seconds and go to 2 - - ... - */ - if (!expr) return CRON_INVALID_INSTANT; - struct tm calval; - memset(&calval, 0, sizeof(struct tm)); - struct tm* calendar = cron_time(&date, &calval); - if (!calendar) return CRON_INVALID_INSTANT; - time_t original = cron_mktime(calendar); - if (CRON_INVALID_INSTANT == original) return CRON_INVALID_INSTANT; - - /* calculate the previous occurrence */ - int res = do_prev(expr, calendar, calendar->tm_year); - if (0 != res) return CRON_INVALID_INSTANT; - - /* check for a match, try from the next second if one wasn't found */ - time_t calculated = cron_mktime(calendar); - if (CRON_INVALID_INSTANT == calculated) return CRON_INVALID_INSTANT; - if (calculated == original) { - /* We arrived at the original timestamp - round up to the next whole second and try again... */ - res = add_to_field(calendar, CRON_CF_SECOND, -1); - if (0 != res) return CRON_INVALID_INSTANT; - res = do_prev(expr, calendar, calendar->tm_year); - if (0 != res) return CRON_INVALID_INSTANT; - } - - return cron_mktime(calendar); -} diff --git a/library/ccronexpr/ccronexpr.h b/library/ccronexpr/ccronexpr.h deleted file mode 100644 index e04a673..0000000 --- a/library/ccronexpr/ccronexpr.h +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2015, alex at staticlibs.net - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* - * File: ccronexpr.h - * Author: alex - * - * Created on February 24, 2015, 9:35 AM - */ - -#ifndef CCRONEXPR_H -#define CCRONEXPR_H - -#if defined(__cplusplus) && !defined(CRON_COMPILE_AS_CXX) -extern "C" { -#endif - -#ifndef ANDROID -#include -#else /* ANDROID */ -#include -#endif /* ANDROID */ - -#include /*added for use if uint*_t data types*/ - -/** - * Parsed cron expression - */ -typedef struct { - uint8_t seconds[8]; - uint8_t minutes[8]; - uint8_t hours[3]; - uint8_t days_of_week[1]; - uint8_t days_of_month[4]; - uint8_t months[2]; -} cron_expr; - -/** - * Parses specified cron expression. - * - * @param expression cron expression as nul-terminated string, - * should be no longer that 256 bytes - * @param pointer to cron expression structure, it's client code responsibility - * to free/destroy it afterwards - * @param error output error message, will be set to string literal - * error message in case of error. Will be set to NULL on success. - * The error message should NOT be freed by client. - */ -void cron_parse_expr(const char* expression, cron_expr* target, const char** error); - -/** - * Uses the specified expression to calculate the next 'fire' date after - * the specified date. All dates are processed as UTC (GMT) dates - * without timezones information. To use local dates (current system timezone) - * instead of GMT compile with '-DCRON_USE_LOCAL_TIME' - * - * @param expr parsed cron expression to use in next date calculation - * @param date start date to start calculation from - * @return next 'fire' date in case of success, '((time_t) -1)' in case of error. - */ -time_t cron_next(cron_expr* expr, time_t date); - -/** - * Uses the specified expression to calculate the previous 'fire' date after - * the specified date. All dates are processed as UTC (GMT) dates - * without timezones information. To use local dates (current system timezone) - * instead of GMT compile with '-DCRON_USE_LOCAL_TIME' - * - * @param expr parsed cron expression to use in previous date calculation - * @param date start date to start calculation from - * @return previous 'fire' date in case of success, '((time_t) -1)' in case of error. - */ -time_t cron_prev(cron_expr* expr, time_t date); - - -#if defined(__cplusplus) && !defined(CRON_COMPILE_AS_CXX) -} /* extern "C"*/ -#endif - -#endif /* CCRONEXPR_H */ - - diff --git a/library/ccronexpr/ccronexpr_test.c b/library/ccronexpr/ccronexpr_test.c deleted file mode 100644 index 74b8d48..0000000 --- a/library/ccronexpr/ccronexpr_test.c +++ /dev/null @@ -1,407 +0,0 @@ -/* - * Copyright 2015, alex at staticlibs.net - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* - * File: CronExprParser_test.cpp - * Author: alex - * - * Created on February 24, 2015, 9:36 AM - */ - -#include -#include -#include -#include -#include - -#include "ccronexpr.h" - -#define MAX_SECONDS 60 -#define CRON_MAX_MINUTES 60 -#define CRON_MAX_HOURS 24 -#define CRON_MAX_DAYS_OF_WEEK 8 -#define CRON_MAX_DAYS_OF_MONTH 32 -#define CRON_MAX_MONTHS 12 - -#define INVALID_INSTANT ((time_t) -1) - -#define DATE_FORMAT "%Y-%m-%d_%H:%M:%S" - -#ifndef ARRAY_LEN -#define ARRAY_LEN(x) sizeof(x)/sizeof(x[0]) -#endif - -#ifdef CRON_TEST_MALLOC -static int cronAllocations = 0; -static int cronTotalAllocations = 0; -static int maxAlloc = 0; -void* cron_malloc(size_t n) { - cronAllocations++; - cronTotalAllocations++; - if (cronAllocations > maxAlloc) { - maxAlloc = cronAllocations; - } - return malloc(n); -} - -void cron_free(void* p) { - cronAllocations--; - free(p); -} -#endif - -#ifndef ANDROID -#ifndef _WIN32 -time_t timegm(struct tm* __tp); -#else /* _WIN32 */ -static time_t timegm(struct tm* tm) { - return _mkgmtime(tm); -} -#endif /* _WIN32 */ -#else /* ANDROID */ -static time_t timegm(struct tm * const t) { - /* time_t is signed on Android. */ - static const time_t kTimeMax = ~(1L << (sizeof (time_t) * CHAR_BIT - 1)); - static const time_t kTimeMin = (1L << (sizeof (time_t) * CHAR_BIT - 1)); - time64_t result = timegm64(t); - if (result < kTimeMin || result > kTimeMax) - return -1; - return result; -} -#endif - -/** - * uint8_t* replace char* for storing hit dates, set_bit and get_bit are used as handlers - */ -uint8_t cron_get_bit(uint8_t* rbyte, int idx); -void cron_set_bit(uint8_t* rbyte, int idx); -void cron_del_bit(uint8_t* rbyte, int idx); - -static int crons_equal(cron_expr* cr1, cron_expr* cr2) { - unsigned int i; - for (i = 0; i < ARRAY_LEN(cr1->seconds); i++) { - if (cr1->seconds[i] != cr2->seconds[i]) { - printf("seconds not equal @%d %02x != %02x", i, cr1->seconds[i], cr2->seconds[i]); - return 0; - } - } - for (i = 0; i < ARRAY_LEN(cr1->minutes); i++) { - if (cr1->minutes[i] != cr2->minutes[i]) { - printf("minutes not equal @%d %02x != %02x", i, cr1->minutes[i], cr2->minutes[i]); - return 0; - } - } - for (i = 0; i < ARRAY_LEN(cr1->hours); i++) { - if (cr1->hours[i] != cr2->hours[i]) { - printf("hours not equal @%d %02x != %02x", i, cr1->hours[i], cr2->hours[i]); - return 0; - } - } - for (i = 0; i < ARRAY_LEN(cr1->days_of_week); i++) { - if (cr1->days_of_week[i] != cr2->days_of_week[i]) { - printf("days_of_week not equal @%d %02x != %02x", i, cr1->days_of_week[i], cr2->days_of_week[i]); - return 0; - } - } - for (i = 0; i < ARRAY_LEN(cr1->days_of_month); i++) { - if (cr1->days_of_month[i] != cr2->days_of_month[i]) { - printf("days_of_month not equal @%d %02x != %02x", i, cr1->days_of_month[i], cr2->days_of_month[i]); - return 0; - } - } - for (i = 0; i < ARRAY_LEN(cr1->months); i++) { - if (cr1->months[i] != cr2->months[i]) { - printf("months not equal @%d %02x != %02x", i, cr1->months[i], cr2->months[i]); - return 0; - } - } - return 1; -} - -int one_dec_num(const char ch) { - switch (ch) { - case '0': - return 0; - case '1': - return 1; - case '2': - return 2; - case '3': - return 3; - case '4': - return 4; - case '5': - return 5; - case '6': - return 6; - case '7': - return 7; - case '8': - return 8; - case '9': - return 9; - default: - return -1; - } -} - -int two_dec_num(const char* first) { - return one_dec_num(first[0]) * 10 + one_dec_num(first[1]); -} - -/* strptime is not available in msvc */ -/* 2012-07-01_09:53:50 */ -/* 0123456789012345678 */ -struct tm* poors_mans_strptime(const char* str) { - struct tm* cal = (struct tm*) malloc(sizeof(struct tm)); - switch (str[3]) { - case '7': - cal->tm_year = 107; - break; - case '8': - cal->tm_year = 108; - break; - case '9': - cal->tm_year = 109; - break; - case '0': - cal->tm_year = 110; - break; - case '1': - cal->tm_year = 111; - break; - case '2': - cal->tm_year = 112; - break; - } - cal->tm_mon = two_dec_num(str + 5) - 1; - cal->tm_mday = two_dec_num(str + 8); - cal->tm_wday = 0; - cal->tm_yday = 0; - cal->tm_hour = two_dec_num(str + 11); - cal->tm_min = two_dec_num(str + 14); - cal->tm_sec = two_dec_num(str + 17); - return cal; -} - -void check_next(const char* pattern, const char* initial, const char* expected) { - const char* err = NULL; - cron_expr parsed; - memset(&parsed, 0, sizeof(parsed)); - cron_parse_expr(pattern, &parsed, &err); - - struct tm* calinit = poors_mans_strptime(initial); - time_t dateinit = timegm(calinit); - assert(-1 != dateinit); - time_t datenext = cron_next(&parsed, dateinit); - struct tm* calnext = gmtime(&datenext); - assert(calnext); - char* buffer = (char*) malloc(21); - memset(buffer, 0, 21); - strftime(buffer, 20, DATE_FORMAT, calnext); - if (0 != strcmp(expected, buffer)) { - printf("Pattern: %s\n", pattern); - printf("Initial: %s\n", initial); - printf("Expected: %s\n", expected); - printf("Actual: %s\n", buffer); - assert(0); - } - free(buffer); - free(calinit); -} - -void check_same(const char* expr1, const char* expr2) { - cron_expr parsed1; - memset(&parsed1, 0, sizeof(parsed1)); - cron_parse_expr(expr1, &parsed1, NULL); - cron_expr parsed2; - memset(&parsed2, 0, sizeof(parsed2)); - cron_parse_expr(expr2, &parsed2, NULL); - assert(crons_equal(&parsed1, &parsed2)); -} - -void check_calc_invalid() { - cron_expr parsed; - memset(&parsed, 0, sizeof(parsed)); - cron_parse_expr("0 0 0 31 6 *", &parsed, NULL); - struct tm * calinit = poors_mans_strptime("2012-07-01_09:53:50"); - time_t dateinit = timegm(calinit); - time_t res = cron_next(&parsed, dateinit); - assert(INVALID_INSTANT == res); - free(calinit); -} - -void check_expr_invalid(const char* expr) { - const char* err = NULL; - cron_expr test; - memset(&test, 0, sizeof(test)); - cron_parse_expr(expr, &test, &err); - assert(err); -} - -void test_expr() { - check_next("*/15 * 1-4 * * *", "2012-07-01_09:53:50", "2012-07-02_01:00:00"); - check_next("*/15 * 1-4 * * *", "2012-07-01_09:53:00", "2012-07-02_01:00:00"); - check_next("0 */2 1-4 * * *", "2012-07-01_09:00:00", "2012-07-02_01:00:00"); - check_next("* * * * * *", "2012-07-01_09:00:00", "2012-07-01_09:00:01"); - check_next("* * * * * *", "2012-12-01_09:00:58", "2012-12-01_09:00:59"); - check_next("10 * * * * *", "2012-12-01_09:42:09", "2012-12-01_09:42:10"); - check_next("11 * * * * *", "2012-12-01_09:42:10", "2012-12-01_09:42:11"); - check_next("10 * * * * *", "2012-12-01_09:42:10", "2012-12-01_09:43:10"); - check_next("10-15 * * * * *", "2012-12-01_09:42:09", "2012-12-01_09:42:10"); - check_next("10-15 * * * * *", "2012-12-01_21:42:14", "2012-12-01_21:42:15"); - check_next("0 * * * * *", "2012-12-01_21:10:42", "2012-12-01_21:11:00"); - check_next("0 * * * * *", "2012-12-01_21:11:00", "2012-12-01_21:12:00"); - check_next("0 11 * * * *", "2012-12-01_21:10:42", "2012-12-01_21:11:00"); - check_next("0 10 * * * *", "2012-12-01_21:11:00", "2012-12-01_22:10:00"); - check_next("0 0 * * * *", "2012-09-30_11:01:00", "2012-09-30_12:00:00"); - check_next("0 0 * * * *", "2012-09-30_12:00:00", "2012-09-30_13:00:00"); - check_next("0 0 * * * *", "2012-09-10_23:01:00", "2012-09-11_00:00:00"); - check_next("0 0 * * * *", "2012-09-11_00:00:00", "2012-09-11_01:00:00"); - check_next("0 0 0 * * *", "2012-09-01_14:42:43", "2012-09-02_00:00:00"); - check_next("0 0 0 * * *", "2012-09-02_00:00:00", "2012-09-03_00:00:00"); - check_next("* * * 10 * *", "2012-10-09_15:12:42", "2012-10-10_00:00:00"); - check_next("* * * 10 * *", "2012-10-11_15:12:42", "2012-11-10_00:00:00"); - check_next("0 0 0 * * *", "2012-09-30_15:12:42", "2012-10-01_00:00:00"); - check_next("0 0 0 * * *", "2012-10-01_00:00:00", "2012-10-02_00:00:00"); - check_next("0 0 0 * * *", "2012-08-30_15:12:42", "2012-08-31_00:00:00"); - check_next("0 0 0 * * *", "2012-08-31_00:00:00", "2012-09-01_00:00:00"); - check_next("0 0 0 * * *", "2012-10-30_15:12:42", "2012-10-31_00:00:00"); - check_next("0 0 0 * * *", "2012-10-31_00:00:00", "2012-11-01_00:00:00"); - check_next("0 0 0 1 * *", "2012-10-30_15:12:42", "2012-11-01_00:00:00"); - check_next("0 0 0 1 * *", "2012-11-01_00:00:00", "2012-12-01_00:00:00"); - check_next("0 0 0 1 * *", "2010-12-31_15:12:42", "2011-01-01_00:00:00"); - check_next("0 0 0 1 * *", "2011-01-01_00:00:00", "2011-02-01_00:00:00"); - check_next("0 0 0 31 * *", "2011-10-30_15:12:42", "2011-10-31_00:00:00"); - check_next("0 0 0 1 * *", "2011-10-30_15:12:42", "2011-11-01_00:00:00"); - check_next("* * * * * 2", "2010-10-25_15:12:42", "2010-10-26_00:00:00"); - check_next("* * * * * 2", "2010-10-20_15:12:42", "2010-10-26_00:00:00"); - check_next("* * * * * 2", "2010-10-27_15:12:42", "2010-11-02_00:00:00"); - check_next("55 5 * * * *", "2010-10-27_15:04:54", "2010-10-27_15:05:55"); - check_next("55 5 * * * *", "2010-10-27_15:05:55", "2010-10-27_16:05:55"); - check_next("55 * 10 * * *", "2010-10-27_09:04:54", "2010-10-27_10:00:55"); - check_next("55 * 10 * * *", "2010-10-27_10:00:55", "2010-10-27_10:01:55"); - check_next("* 5 10 * * *", "2010-10-27_09:04:55", "2010-10-27_10:05:00"); - check_next("* 5 10 * * *", "2010-10-27_10:05:00", "2010-10-27_10:05:01"); - check_next("55 * * 3 * *", "2010-10-02_10:05:54", "2010-10-03_00:00:55"); - check_next("55 * * 3 * *", "2010-10-03_00:00:55", "2010-10-03_00:01:55"); - check_next("* * * 3 11 *", "2010-10-02_14:42:55", "2010-11-03_00:00:00"); - check_next("* * * 3 11 *", "2010-11-03_00:00:00", "2010-11-03_00:00:01"); - check_next("0 0 0 29 2 *", "2007-02-10_14:42:55", "2008-02-29_00:00:00"); - check_next("0 0 0 29 2 *", "2008-02-29_00:00:00", "2012-02-29_00:00:00"); - check_next("0 0 7 ? * MON-FRI", "2009-09-26_00:42:55", "2009-09-28_07:00:00"); - check_next("0 0 7 ? * MON-FRI", "2009-09-28_07:00:00", "2009-09-29_07:00:00"); - check_next("0 30 23 30 1/3 ?", "2010-12-30_00:00:00", "2011-01-30_23:30:00"); - check_next("0 30 23 30 1/3 ?", "2011-01-30_23:30:00", "2011-04-30_23:30:00"); - check_next("0 30 23 30 1/3 ?", "2011-04-30_23:30:00", "2011-07-30_23:30:00"); -} - -void test_parse() { - - check_same("* * * 2 * *", "* * * 2 * ?"); - check_same("57,59 * * * * *", "57/2 * * * * *"); - check_same("1,3,5 * * * * *", "1-6/2 * * * * *"); - check_same("* * 4,8,12,16,20 * * *", "* * 4/4 * * *"); - check_same("* * * * * 0-6", "* * * * * TUE,WED,THU,FRI,SAT,SUN,MON"); - check_same("* * * * * 0", "* * * * * SUN"); - check_same("* * * * * 0", "* * * * * 7"); - check_same("* * * * 1-12 *", "* * * * FEB,JAN,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC *"); - check_same("* * * * 2 *", "* * * * Feb *"); - check_same("* * * * 1 *", "* * * * 1 *"); - - check_expr_invalid("77 * * * * *"); - check_expr_invalid("44-77 * * * * *"); - check_expr_invalid("* 77 * * * *"); - check_expr_invalid("* 44-77 * * * *"); - check_expr_invalid("* * 27 * * *"); - check_expr_invalid("* * 23-28 * * *"); - check_expr_invalid("* * * 45 * *"); - check_expr_invalid("* * * 28-45 * *"); - check_expr_invalid("0 0 0 25 13 ?"); - check_expr_invalid("0 0 0 25 0 ?"); - check_expr_invalid("0 0 0 32 12 ?"); - check_expr_invalid("* * * * 11-13 *"); - check_expr_invalid("-5 * * * * *"); - check_expr_invalid("3-2 */5 * * * *"); - check_expr_invalid("/5 * * * * *"); - check_expr_invalid("*/0 * * * * *"); - check_expr_invalid("*/-0 * * * * *"); - check_expr_invalid("* 1 1 0 * *"); -} - -void test_bits() { - - uint8_t testbyte[8]; - memset(testbyte, 0, 8); - int err = 0; - int i; - - for (i = 0; i <= 63; i++) { - cron_set_bit(testbyte, i); - if (!cron_get_bit(testbyte, i)) { - printf("Bit set error! Bit: %d!\n", i); - err = 1; - } - cron_del_bit(testbyte, i); - if (cron_get_bit(testbyte, i)) { - printf("Bit clear error! Bit: %d!\n", i); - err = 1; - } - assert(!err); - } - - for (i = 0; i < 12; i++) { - cron_set_bit(testbyte, i); - } - if (testbyte[0] != 0xff) { - err = 1; - } - if (testbyte[1] != 0x0f) { - err = 1; - } - - assert(!err); -} - -/* For this test to work you need to set "-DCRON_TEST_MALLOC=1"*/ -#ifdef CRON_TEST_MALLOC -void test_memory() { - cron_expr cron; - const char* err; - - cron_parse_expr("* * * * * *", &cron, &err); - if (cronAllocations != 0) { - printf("Allocations != 0 but %d", cronAllocations); - assert(0); - } - printf("Allocations: total: %d, max: %d", cronTotalAllocations, maxAlloc); -} -#endif - -int main() { - - test_bits(); - - test_expr(); - test_parse(); - check_calc_invalid(); - #ifdef CRON_TEST_MALLOC - test_memory(); /* For this test to work you need to set "-DCRON_TEST_MALLOC=1"*/ - #endif - printf("\nAll OK!"); - return 0; -} - diff --git a/library/jobs/jobs.c b/library/jobs/jobs.c index c135f1f..cce111b 100644 --- a/library/jobs/jobs.c +++ b/library/jobs/jobs.c @@ -36,13 +36,25 @@ static struct .semaphore = NULL, .init = 0 }; - -void cron_job_list_init() +void cron_job_list_dinit(){ + linked_link_state.init = 0; + linked_link_state.next_id = 0; + struct cron_job_node * node = cron_job_list_first(); + while(node){ + cron_job_list_remove(node->job->id); + node = cron_job_list_first(); + } + linked_link_state.first = NULL; + linked_link_state.semaphore = NULL; +} +int cron_job_list_init() { if (linked_link_state.init == 0) { linked_link_state.semaphore = xSemaphoreCreateMutex(); linked_link_state.init=1; + return 0; } + return -1; } struct cron_job_node *cron_job_list_first() @@ -70,16 +82,14 @@ int cron_job_list_insert(cron_job *job) if (linked_link_state.semaphore == NULL) cron_job_list_init(); if (job == NULL) - { return -1; - } struct cron_job_node *new_node = calloc(sizeof(struct cron_job_node),1); if (new_node == NULL) { return -1; } new_node->job = job; - if (xSemaphoreTake(linked_link_state.semaphore, (TickType_t)10) == pdTRUE) + if (xSemaphoreTake(linked_link_state.semaphore, (TickType_t)100) == pdTRUE) { linked_link_state.first = _cron_job_list_insert(linked_link_state.first, new_node); xSemaphoreGive(linked_link_state.semaphore); @@ -96,11 +106,11 @@ int cron_job_list_insert(cron_job *job) int cron_job_list_remove(int id) { - int ret = -1; + int ret = -5; struct cron_job_node *node = linked_link_state.first, *prev_node = NULL; - if (xSemaphoreTake(linked_link_state.semaphore, (TickType_t)10) == pdTRUE) + if (xSemaphoreTake(linked_link_state.semaphore, (TickType_t)100) == pdTRUE) { - do + while (node) { if (node->job->id == id) { @@ -114,7 +124,6 @@ int cron_job_list_remove(int id) } free(node); node = NULL; - ret = 0; break; } @@ -123,8 +132,7 @@ int cron_job_list_remove(int id) prev_node = node; node = node->next; } - - } while (node->next); + } xSemaphoreGive(linked_link_state.semaphore); } else @@ -154,10 +162,11 @@ int cron_job_node_count() } -int cron_job_list_reset_id(){ +int cron_job_list_reset_id() +{ if (cron_job_node_count()==0) { linked_link_state.next_id=0; return 0; - } + } return -1; } \ No newline at end of file diff --git a/library/jobs/jobs.h b/library/jobs/jobs.h index dbb242d..465043b 100644 --- a/library/jobs/jobs.h +++ b/library/jobs/jobs.h @@ -22,6 +22,8 @@ #include #include "cron.h" +// LINKED LIST JOB STRUCTURE + struct cron_job_node { struct cron_job_node * next; cron_job * job; @@ -64,14 +66,25 @@ int cron_job_list_remove(int id); * RETURNS: number of nodes on list */ int cron_job_node_count(); + /* * SUMMARY: initializes the needed structures for the module to work (like mutex). It is safe to call it multiple times. * * PARAMS: NONE * +* RETURNS:zero on success +*/ +int cron_job_list_init(); + +/* +* SUMMARY: Remove initialized vars of the module for testing proupose +* +* PARAMS: NONE +* * RETURNS:none */ -void cron_job_list_init(); +void cron_job_list_dinit(); + /* * SUMMARY: If the node count is zero then make id 0 to start over. * diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..8480884 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,6 @@ +set(COMPONENT_SRCDIRS ".") +set(COMPONENT_ADD_INCLUDEDIRS ".") + +set(COMPONENT_REQUIRES unity esp_cron) + +register_component() diff --git a/test/component.mk b/test/component.mk index f1b682f..1ee4f1e 100644 --- a/test/component.mk +++ b/test/component.mk @@ -1,2 +1,2 @@ -CFLAGS += -D CRON_USE_LOCAL_TIME -COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive \ No newline at end of file +CPPFLAGS += -D CRON_USE_LOCAL_TIME +COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive diff --git a/test/test_cron.c b/test/test_cron.c index f1ec9aa..c4bf5f7 100644 --- a/test/test_cron.c +++ b/test/test_cron.c @@ -25,18 +25,22 @@ #include "unity.h" #include "cron.h" #include "jobs.h" - +void reset_cron(){ + cron_job_init(); + cron_stop(); +} TEST_CASE("**CRON_JOB - INFO -- INIT TEST, THIS INITIALIZES THE TIME DATA MAY LEAK SOME MEMORY", "[cron_job]") { struct timeval tv; tv.tv_sec = 1530000000; // SOMEWHERE IN JUNE 2018 settimeofday(&tv, NULL); - + cron_job_init(); } TEST_CASE("**CRON_JOB - cron_job_schedule and cron_job_remove IS IT WORKING? ", "[cron_job]") -{ +{ + reset_cron(); int cnt = 0, cnt2 = 0, ans = 0; cnt = cron_job_node_count(); @@ -48,10 +52,13 @@ TEST_CASE("**CRON_JOB - cron_job_schedule and cron_job_remove IS IT WORKING? ", cnt2 = cron_job_node_count(); TEST_ASSERT_EQUAL_INT_MESSAGE( 0, ans, "DESTROY FAILED WITH ERRORS"); TEST_ASSERT_EQUAL_INT_MESSAGE(cnt, cnt2, "LIST DIDNT REDUCE"); + // cron_stop(); + vTaskDelay((1 * 1000) / portTICK_PERIOD_MS); } TEST_CASE("**CRON_JOB - cron_job_schedule CRON PARSER AND SCHEDULER IS AS EXPECTED ", "[cron_job]") { + reset_cron(); time_t now; struct timeval tv; struct tm timeinfo; @@ -61,6 +68,7 @@ TEST_CASE("**CRON_JOB - cron_job_schedule CRON PARSER AND SCHEDULER IS AS EXPECT tv.tv_sec = 1530000000; // SOMEWHERE IN JUNE 2018 settimeofday(&tv, NULL); cron_job * job=cron_job_create("* * * * * *",NULL,NULL); + cron_stop(); time(&now); localtime_r(&(job->next_execution), &timeinfo); strftime(buffer, buffer_len, "%c", &timeinfo); @@ -71,17 +79,21 @@ TEST_CASE("**CRON_JOB - cron_job_schedule CRON PARSER AND SCHEDULER IS AS EXPECT TEST_ASSERT_EQUAL_INT_MESSAGE(now + 1, job->next_execution, "SCHEDULE IS NOT FOR THE NEXT SECOND AS IT SHOULD BE"); ans = cron_job_destroy(job); TEST_ASSERT_EQUAL_INT_MESSAGE(0, ans, "UNABLE TO DESTROY A JOB"); + cron_stop(); + // vTaskDelay((1 * 1000) / portTICK_PERIOD_MS); + } TEST_CASE("**CRON_JOB - cron_clear_all TEST CLEAR ALL JOBS ", "[cron_job]") { + reset_cron(); int cnt_init=0,cnt=0,i=0; cron_job * jobs[10]; cnt_init = cron_job_node_count(); - for (i =0;i<10;i++) { jobs[i]=cron_job_create("* * * * * *",NULL,NULL); } + cron_stop(); cnt = cron_job_node_count(); TEST_ASSERT_EQUAL_INT_MESSAGE(i, cnt-cnt_init,"Creation count"); cron_job_destroy(jobs[0]); @@ -110,6 +122,7 @@ void test_cron_job_sample_callback(cron_job *job) TEST_CASE("**CRON_JOB - cron_schedule_task TEST IF THE SCHEDULER RUNS ONCE AND WITH EXPECTED RESULTS", "[cron_job]") { + reset_cron(); time_t begin=1530000000; const int seconds_to_run=10*2; //whatever you want to run must be twice because one for the delay and one for the callback :P cron_job * jobs[2]; @@ -121,14 +134,276 @@ TEST_CASE("**CRON_JOB - cron_schedule_task TEST IF THE SCHEDULER RUNS ONCE AND W for (int i =0;idata > 5*8, "Unexpected delay");//IT DEPENDS ON TIME MOST OF THE TIME WILL DO 45, SOME 40 AND OTHERS 50 - TEST_ASSERT_MESSAGE((int)jobs[1]->data > 10005, "Unexpected delay"); // SOMETIMES IT RUNS TWICE BECAUSE OF BEGIN TIME + vTaskDelay((2 * 1000) / portTICK_PERIOD_MS); + TEST_ASSERT_MESSAGE((int)jobs[0]->data >= 5*8, "Unexpected delay");//IT DEPENDS ON TIME MOST OF THE TIME WILL DO 45, SOME 40 AND OTHERS 50 + TEST_ASSERT_MESSAGE((int)jobs[1]->data >= 10005, "Unexpected delay"); // SOMETIMES IT RUNS TWICE BECAUSE OF BEGIN TIME cron_job_destroy(jobs[0]); cron_job_destroy(jobs[1]); //cron_job_clear_all(); } +TEST_CASE("**CRON_JOB - cron_schedule_task TEST IF THE SCHEDULER RUNS WHILE STARTED", "[cron_job]") +{ + reset_cron(); + time_t begin=1530000000; + cron_job * job; + struct timeval tv; + tv.tv_sec = begin; // SOMEWHERE IN JUNE 2018 + settimeofday(&tv, NULL); + job=cron_job_create("* * * * * *",test_cron_job_sample_callback,(void *)0); + vTaskDelay((1 * 1000) / portTICK_PERIOD_MS); // WAIT FOR THE TIME TO SETTLE + int res = cron_start(); + TEST_ASSERT_EQUAL_INT_MESSAGE(Cron_ok,res, "Cron did not start");//IT DEPENDS ON TIME MOST OF THE TIME WILL DO 45, SOME 40 AND OTHERS 50 + vTaskDelay((6 * 1000) / portTICK_PERIOD_MS); // WAIT FOR THE TASK TO RUN - WE DO 6 SECONDS BUT CHECK AGAINST 5 ASSUMING SOME ERRORS ON THE DELAY BETWEEN A TASK CREATION AND STUFF + cron_stop(); + TEST_ASSERT_MESSAGE((int)job->data > 5*5, "Unexpected delay");//IT DEPENDS ON TIME + cron_job_destroy(job); + //cron_job_clear_all(); +} + +TEST_CASE("**CRON_JOB - cron_job_create() should return a job with the data 1 2 3 * * *", "[cron_job]") +{ + reset_cron(); + cron_job * job = cron_job_create("1 2 3 * * *",NULL,(char *)"the data"); + TEST_ASSERT_MESSAGE(job!=NULL, "JOB IS NULL"); + TEST_ASSERT_EQUAL_INT_MESSAGE(strlen(job->data), 8, "the data len is not 11"); + TEST_ASSERT_EQUAL_INT_MESSAGE(strncmp(job->data,"the data",8), 0, "the job data is not 1 2 3 * * *"); + vTaskDelay((1 * 1000) / portTICK_PERIOD_MS); + cron_job_clear_all(); +} + +TEST_CASE("**CRON_JOB - cron_job_create() NULL if the schedule is wrong", "[cron_job]") +{ + reset_cron(); + cron_job * job = cron_job_create("1 2 3",NULL,(char *)"the data"); + TEST_ASSERT_MESSAGE(job == NULL, "the job is not null"); +} + +TEST_CASE("**CRON_JOB - cron_job_destroy() return 0 if job is a cron_job", "[cron_job]") +{ + reset_cron(); + cron_job * job = cron_job_create("* * * * * *",NULL,(char *)"the data"); + vTaskDelay((1 * 1000) / portTICK_PERIOD_MS); + int res = cron_job_destroy(job); + TEST_ASSERT_EQUAL_INT_MESSAGE(res, 0, "the return isnt 0"); + cron_job_clear_all(); +} + +TEST_CASE("**CRON_JOB - cron_job_destroy() return 0 if after 2 jobs creation but just one is destroyed", "[cron_job]") +{ + reset_cron(); + (void) cron_job_create("1 2 3 * * *",NULL,(char *)"the data"); + cron_job * job2 = cron_job_create("1 2 5 * * *",NULL,(char *)"the data"); + cron_stop(); + int res = cron_job_destroy(job2); + TEST_ASSERT_EQUAL_INT_MESSAGE(0, res, "the return isnt 0"); + vTaskDelay((1 * 1000) / portTICK_PERIOD_MS); + cron_job_clear_all(); +} + +TEST_CASE("**CRON_JOB - cron_job_destroy() return 0 if after 4 jobs creation but just one is destroyed", "[cron_job]") +{ + reset_cron(); + (void) cron_job_create("1 2 3 * * *",NULL,(char *)"the data"); + (void) cron_job_create("1 2 5 * * *",NULL,(char *)"the data"); + cron_job * job3 = cron_job_create("3 2 5 * * *",NULL,(char *)"the data"); + (void) cron_job_create("4 2 5 * * *",NULL,(char *)"the data"); + cron_stop(); + int res = cron_job_destroy(job3); + TEST_ASSERT_EQUAL_INT_MESSAGE(res, 0, "the return isnt 0"); + cron_job_clear_all(); +} + +TEST_CASE("**CRON_JOB - cron_job_destroy() return -1 if job is null", "[cron_job]") +{ + reset_cron(); + int res = cron_job_destroy(NULL); + TEST_ASSERT_EQUAL_INT_MESSAGE(Cron_bad_job, res, "the return isnt -1"); +} + + +TEST_CASE("**CRON_JOB - cron_job_clear_all() anfter execute cron_job_clear_all the job is null", "[cron_job]") +{ + reset_cron(); + (void) cron_job_create("1 2 3 * * *",NULL,(char *)"the data"); + int res = cron_job_clear_all(); + TEST_ASSERT_EQUAL_INT_MESSAGE(res, 0, "return of clear is not zero"); +} + + +TEST_CASE("**CRON_JOB - cron_job_clear_all() called with an empty list", "[cron_job]") +{ + reset_cron(); + (void) cron_job_clear_all(); + int res = cron_job_clear_all(); + TEST_ASSERT_EQUAL_INT_MESSAGE(res, 0, "return of clear is not zero"); +} + +TEST_CASE("**CRON_JOB - cron_stop() return 0 if cron is started", "[cron_job]") +{ + reset_cron(); + cron_start(); + // (void) cron_job_create("1 2 3 * * *",NULL,"the data"); + int res = cron_stop(); + TEST_ASSERT_EQUAL_INT_MESSAGE(res, 0, "return of cron_stop is not zero"); +} + + +TEST_CASE("**CRON_JOB - cron_stop() return -1 if cron is not running and method is called again", "[cron_job]") +{ + reset_cron(); + (void) cron_stop(); + int res = cron_stop(); + TEST_ASSERT_EQUAL_INT_MESSAGE(res, Cron_is_stopped, "return of cron_stop is not -1"); +} + +TEST_CASE("**CRON_JOB - cron_start() return -1 if cron is already started", "[cron_job]") +{ + reset_cron(); + cron_start(); + (void) cron_job_create("1 2 3 * * *",NULL,(char *)"the data"); + int res = cron_start(); + TEST_ASSERT_EQUAL_INT_MESSAGE(res, Cron_not_stopped, "return of cron_start is not -1"); + vTaskDelay((1 * 1000) / portTICK_PERIOD_MS); + (void) cron_job_clear_all(); +} +TEST_CASE("**CRON_JOB - cron_job_schedule() return -1 if job is null", "[cron_job]") +{ + reset_cron(); + int res = cron_job_schedule(NULL); + TEST_ASSERT_EQUAL_INT_MESSAGE(res, Cron_bad_job, "res is not -1"); +} + +TEST_CASE("**CRON_JOB - cron_job_unschedule() return -1 if job is null", "[cron_job]") +{ + reset_cron(); + int res = cron_job_unschedule(NULL); + TEST_ASSERT_EQUAL_INT_MESSAGE(res, Cron_bad_job, "res is not -1"); +} + +TEST_CASE("**CRON_JOB - cron_job_unschedule() return 0 if job is ok", "[cron_job]") +{ + reset_cron(); + cron_job * job = cron_job_create("3 2 5 * * *",NULL,(char *)"the data"); + cron_stop(); + int res = cron_job_unschedule(job); + TEST_ASSERT_EQUAL_INT_MESSAGE(res, 0, "res is not 0"); +} + +TEST_CASE("**CRON_JOB - cron_job_load_expression() return 0 if job and schedule are correct", "[cron_job]") +{ + reset_cron(); + cron_job_list_init(); // CALL THIS ON ANY CREATE + cron_job *job = calloc(sizeof(cron_job), 1); + job->callback = NULL; + job->data = (char *)"3 2 5 * * *"; + job->id = -1; + job->load = NULL; + int res = cron_job_load_expression(job, "3 2 5 * * *"); + cron_stop(); + TEST_ASSERT_EQUAL_INT_MESSAGE(res, 0, "res is not 0"); +} + +TEST_CASE("**CRON_JOB - cron_job_load_expression() return -1 if schedule is null", "[cron_job]") +{ + reset_cron(); + cron_job_list_init(); // CALL THIS ON ANY CREATE + cron_job *job = calloc(sizeof(cron_job), 1); + job->callback = NULL; + job->data = (char *)"3 2 5 * * *"; + job->id = -1; + job->load = NULL; + int res = cron_job_load_expression(job, NULL); + cron_stop(); + TEST_ASSERT_EQUAL_INT_MESSAGE(res, Cron_bad_schedule, "res is not -1"); +} +TEST_CASE("**CRON_JOB - cron_job_load_expression() return -2 if schedule is wrong 3 2 5", "[cron_job]") +{ + reset_cron(); + cron_job_list_init(); // CALL THIS ON ANY CREATE + cron_job *job = calloc(sizeof(cron_job), 1); + job->callback = NULL; + job->data = (char *)"3 2 5"; + job->id = -1; + job->load = NULL; + int res = cron_job_load_expression(job, "3 2 5"); + cron_stop(); + TEST_ASSERT_EQUAL_INT_MESSAGE(res, Cron_parse_expresion_error, "res is not -1"); +} +TEST_CASE("**CRON_JOB - cron_job_load_expression() return -2 if schedule is wrong *****", "[cron_job]") +{ + reset_cron(); + cron_job_list_init(); // CALL THIS ON ANY CREATE + cron_job *job = calloc(sizeof(cron_job), 1); + job->callback = NULL; + job->data = (char *)"3 2 5"; + job->id = -1; + job->load = NULL; + int res = cron_job_load_expression(job, "*****"); + cron_stop(); + TEST_ASSERT_EQUAL_INT_MESSAGE(res, Cron_parse_expresion_error, "res is not -1"); +} +TEST_CASE("**CRON_JOB - cron_job_load_expression() return -2 if schedule is wrong 123456789", "[cron_job]") +{ + reset_cron(); + cron_job_list_init(); // CALL THIS ON ANY CREATE + cron_job *job = calloc(sizeof(cron_job), 1); + job->callback = NULL; + job->data = (char *)"3 2 5"; + job->id = -1; + job->load = NULL; + int res = cron_job_load_expression(job, "123456789"); + cron_stop(); + TEST_ASSERT_EQUAL_INT_MESSAGE(res, Cron_parse_expresion_error, "res is not -1"); +} +TEST_CASE("**CRON_JOB - cron_job_load_expression() return -2 if schedule is wrong asdfghjkl", "[cron_job]") +{ + reset_cron(); + cron_job_list_init(); // CALL THIS ON ANY CREATE + cron_job *job = calloc(sizeof(cron_job), 1); + job->callback = NULL; + job->data = (char *)"3 2 5"; + job->id = -1; + job->load = NULL; + int res = cron_job_load_expression(job, "asdfghjkl"); + cron_stop(); + TEST_ASSERT_EQUAL_INT_MESSAGE(res, Cron_parse_expresion_error, "res is not -1"); + free(job); +} + + +TEST_CASE("**CRON_JOB - cron_job_has_loaded() return 0 if job is scheduled", "[cron_job]") +{ + reset_cron(); + cron_job * job = cron_job_create("3 2 5 * * *",NULL,(char *)"the data"); + int res = cron_job_has_loaded(job); + TEST_ASSERT_EQUAL_INT_MESSAGE(res, 0, "res is not 0"); + cron_stop(); +} + +TEST_CASE("**CRON_JOB - cron_job_has_loaded() return -2 if job is scheduled", "[cron_job]") +{ + reset_cron(); + int res = cron_job_has_loaded(NULL); + TEST_ASSERT_EQUAL_INT_MESSAGE(res, Cron_bad_job, "res is not -2"); + cron_stop(); +} + +TEST_CASE("**CRON_JOB - cron_job_has_loaded() return Cron_expresion_not_loaded if job is not scheduled", "[cron_job]") +{ + reset_cron(); + cron_job_list_init(); // CALL THIS ON ANY CREATE + cron_job *job = calloc(sizeof(cron_job), 1); + job->callback = NULL; + job->data = (char *)"3 2 5"; + job->id = -1; + job->load = NULL; + int res = cron_job_has_loaded(job); + TEST_ASSERT_EQUAL_INT_MESSAGE(res, Cron_expresion_not_loaded, "res is not -2"); + cron_stop(); + free(job); +} \ No newline at end of file diff --git a/test/test_cron_list.c b/test/test_cron_list.c new file mode 100644 index 0000000..baedd86 --- /dev/null +++ b/test/test_cron_list.c @@ -0,0 +1,218 @@ +#include +#include "unity.h" +#include "freertos/FreeRTOS.h" + +#include "jobs.h" +#include "cron.h" + +cron_job * test_create_job(char * data, int id){ + cron_job *job = calloc(sizeof(cron_job), 1); + job->callback = NULL; + job->data = data; + job->id = id; + job->load = NULL; + return job; +} + +TEST_CASE("**CRON_JOB - cron_job_list_init() return 0 if module is not initialized", "[cron_job_list]") +{ + cron_job_list_dinit(); + int res = cron_job_list_init(); + TEST_ASSERT_EQUAL_INT_MESSAGE(res, 0, "res is not 0"); + cron_job_list_dinit(); +} + +TEST_CASE("**CRON_JOB - cron_job_has_loaded() return -1 if module is already initialized", "[cron_job_list]") +{ + cron_job_list_dinit(); + (void) cron_job_list_init(); + int res = cron_job_list_init(); + TEST_ASSERT_EQUAL_INT_MESSAGE(res, -1, "res is not -1"); + cron_job_list_dinit(); +} +TEST_CASE("**CRON_JOB - cron_job_list_insert() return -1 if job is NULL", "[cron_job_list]") +{ + cron_job_list_dinit(); + (void) cron_job_list_init(); + int res = cron_job_list_insert(NULL); + TEST_ASSERT_EQUAL_INT_MESSAGE(res, -1, "response is not -1"); + cron_job_list_dinit(); +} +TEST_CASE("**CRON_JOB - cron_job_list_insert() return the new node id if the node is added to list", "[cron_job_list]") +{ + cron_job_list_dinit(); + (void) cron_job_list_init(); + cron_job * job = test_create_job("test1", 8); + int res = cron_job_list_insert(job); + TEST_ASSERT_EQUAL_INT_MESSAGE(res, 8, "response is not 8"); + free(job); + cron_job_list_dinit(); +} +TEST_CASE("**CRON_JOB - cron_job_list_insert() return the new node id if the node is added to list but id is -1", "[cron_job_list]") +{ + cron_job_list_dinit(); + (void) cron_job_list_init(); + cron_job * job = test_create_job("test1", -1); + int res = cron_job_list_insert(job); + TEST_ASSERT_EQUAL_INT_MESSAGE(res, 0, "response is not 0"); + free(job); + cron_job_list_dinit(); +} +TEST_CASE("**CRON_JOB - cron_job_list_insert() return the new node id if the node is added to list while inserting 2 nodes", "[cron_job_list]") +{ + cron_job_list_dinit(); + (void) cron_job_list_init(); + cron_job * job = test_create_job("test1", 8); + int res = cron_job_list_insert(job); + TEST_ASSERT_EQUAL_INT_MESSAGE(res, 8, "response is not 8"); + cron_job * job2 = test_create_job("test1", 1); + res = cron_job_list_insert(job2); + TEST_ASSERT_EQUAL_INT_MESSAGE(res, 1, "response is not 1"); + free(job); + free(job2); + cron_job_list_dinit(); +} +TEST_CASE("**CRON_JOB - cron_job_list_insert() add two nodes width the same id", "[cron_job_list]") +{ + cron_job_list_dinit(); + (void) cron_job_list_init(); + cron_job * job = test_create_job("test1", 1); + int res = cron_job_list_insert(job); + TEST_ASSERT_EQUAL_INT_MESSAGE(res, 1, "response is not 1"); + cron_job * job2 = test_create_job("test1", 1); + res = cron_job_list_insert(job2); + TEST_ASSERT_EQUAL_INT_MESSAGE(res, 1, "response is not 1"); + free(job); + free(job2); + cron_job_list_dinit(); +} + +TEST_CASE("**CRON_JOB - cron_job_list_remove() return 0 if the node exist", "[cron_job_list]") +{ + cron_job_list_dinit(); + (void) cron_job_list_init(); + cron_job * job = test_create_job("test", -1); + (void) cron_job_list_insert(job); + int res = cron_job_list_remove(0); + TEST_ASSERT_EQUAL_INT_MESSAGE(res, 0, "response is not 0"); + free(job); + cron_job_list_dinit(); +} + +TEST_CASE("**CRON_JOB - cron_job_list_remove() return -5 if the node exist", "[cron_job_list]") +{ + cron_job_list_dinit(); + (void) cron_job_list_init(); + cron_job * job = test_create_job("test", -1); + (void) cron_job_list_insert(job); + cron_job * job2 = test_create_job("test", -1); + (void) cron_job_list_insert(job2); + int res = cron_job_list_remove(10); + TEST_ASSERT_EQUAL_INT_MESSAGE(res, -5, "response is not -5"); + free(job); + free(job2); + cron_job_list_dinit(); +} + +TEST_CASE("**CRON_JOB - cron_job_list_remove() return 0 when remove the last element", "[cron_job_list]") +{ + cron_job_list_dinit(); + (void) cron_job_list_init(); + cron_job * job = test_create_job("test", 1); + (void) cron_job_list_insert(job); + cron_job * job2 = test_create_job("test", 2); + (void) cron_job_list_insert(job2); + int res = cron_job_list_remove(2); + TEST_ASSERT_EQUAL_INT_MESSAGE(res, 0, "response is not 0"); + free(job); + free(job2); + cron_job_list_dinit(); +} + +TEST_CASE("**CRON_JOB - cron_job_list_remove() return 0 when remove the first element", "[cron_job_list]") +{ + cron_job_list_dinit(); + (void) cron_job_list_init(); + cron_job * job = test_create_job("test", 1); + (void) cron_job_list_insert(job); + cron_job * job2 = test_create_job("test", 2); + (void) cron_job_list_insert(job2); + int res = cron_job_list_remove(1); + TEST_ASSERT_EQUAL_INT_MESSAGE(res, 0, "response is not 0"); + free(job); + free(job2); + cron_job_list_dinit(); +} + +TEST_CASE("**CRON_JOB - cron_job_node_count() return 0 if there is no nodes", "[cron_job_list]") +{ + cron_job_list_dinit(); + int res = cron_job_node_count(); + TEST_ASSERT_EQUAL_INT_MESSAGE(res, 0, "res is not 0"); + cron_job_list_dinit(); +} + +TEST_CASE("**CRON_JOB - cron_job_node_count() return 2 if there is two nodes", "[cron_job_list]") +{ + cron_job_list_dinit(); + (void) cron_job_list_init(); + cron_job * job = test_create_job("test", 1); + (void) cron_job_list_insert(job); + cron_job * job2 = test_create_job("test", 2); + (void) cron_job_list_insert(job2); + int res = cron_job_node_count(); + TEST_ASSERT_EQUAL_INT_MESSAGE(res, 2, "res is not 2"); + free(job); + free(job2); + cron_job_list_dinit(); +} + +TEST_CASE("**CRON_JOB - cron_job_node_count() return 5 if there is five nodes", "[cron_job_list]") +{ + cron_job_list_dinit(); + (void) cron_job_list_init(); + cron_job * job = test_create_job("test", 1); + (void) cron_job_list_insert(job); + cron_job * job2 = test_create_job("test", 2); + (void) cron_job_list_insert(job2); + cron_job * job3 = test_create_job("test", 3); + (void) cron_job_list_insert(job3); + cron_job * job4 = test_create_job("test", 4); + (void) cron_job_list_insert(job4); + cron_job * job5 = test_create_job("test", 5); + (void) cron_job_list_insert(job5); + int res = cron_job_node_count(); + TEST_ASSERT_EQUAL_INT_MESSAGE(res, 5, "res is not 5"); + free(job); + free(job2); + free(job3); + free(job4); + free(job5); + cron_job_list_dinit(); +} + +TEST_CASE("**CRON_JOB - cron_job_node_count() return 3 if there is five nodes but removes 2", "[cron_job_list]") +{ + cron_job_list_dinit(); + (void) cron_job_list_init(); + cron_job * job = test_create_job("test", 1); + (void) cron_job_list_insert(job); + cron_job * job2 = test_create_job("test", 2); + (void) cron_job_list_insert(job2); + cron_job * job3 = test_create_job("test", 3); + (void) cron_job_list_insert(job3); + cron_job * job4 = test_create_job("test", 4); + (void) cron_job_list_insert(job4); + cron_job * job5 = test_create_job("test", 5); + (void) cron_job_list_insert(job5); + (void) cron_job_list_remove(2); + (void) cron_job_list_remove(4); + int res = cron_job_node_count(); + TEST_ASSERT_EQUAL_INT_MESSAGE(res, 3, "res is not 3"); + free(job); + free(job2); + free(job3); + free(job4); + free(job5); + cron_job_list_dinit(); +} \ No newline at end of file