Initial checkin.

This commit is contained in:
Pim van Pelt
2018-01-05 16:58:15 +01:00
commit 4679636fdc
42 changed files with 9600 additions and 0 deletions

2
unittest/.gitignore vendored Normal file

@ -0,0 +1,2 @@
*.o
test

26
unittest/Makefile Normal file

@ -0,0 +1,26 @@
TARGET = test
LIBS =
CC = gcc
CFLAGS = -g -Wall -I../include -I./ -I ./mongoose/
.PHONY: default all clean
default: $(TARGET)
all: default
OBJECTS = $(patsubst %.c, %.o, $(wildcard *.c))
SRCS = frozen/frozen.c ../src/channel.c ../src/mqtt.c ../src/led.c
HEADERS = $(wildcard *.h)
%.o: %.c $(HEADERS)
$(CC) $(CFLAGS) -c $< -o $@
.PRECIOUS: $(TARGET) $(OBJECTS)
$(TARGET): $(OBJECTS)
$(CC) $(CFLAGS) $(OBJECTS) $(LIBS) $(SRCS) -o $@
clean:
-rm -f $(OBJECTS)
-rm -f $(TARGET)

1373
unittest/frozen/frozen.c Normal file

File diff suppressed because it is too large Load Diff

300
unittest/frozen/frozen.h Normal file

@ -0,0 +1,300 @@
/*
* Copyright (c) 2004-2013 Sergey Lyubka <valenok@gmail.com>
* Copyright (c) 2013 Cesanta Software Limited
* All rights reserved
*
* This library is dual-licensed: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation. For the terms of this
* license, see <http: *www.gnu.org/licenses/>.
*
* You are free to use this library under the terms of the GNU General
* Public License, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* Alternatively, you can license this library under a commercial
* license, as set out in <http://cesanta.com/products.html>.
*/
#ifndef CS_FROZEN_FROZEN_H_
#define CS_FROZEN_FROZEN_H_
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#if defined(_WIN32) && _MSC_VER < 1700
typedef int bool;
enum { false = 0, true = 1 };
#else
#include <stdbool.h>
#endif
/* JSON token type */
enum json_token_type {
JSON_TYPE_INVALID = 0, /* memsetting to 0 should create INVALID value */
JSON_TYPE_STRING,
JSON_TYPE_NUMBER,
JSON_TYPE_TRUE,
JSON_TYPE_FALSE,
JSON_TYPE_NULL,
JSON_TYPE_OBJECT_START,
JSON_TYPE_OBJECT_END,
JSON_TYPE_ARRAY_START,
JSON_TYPE_ARRAY_END,
JSON_TYPES_CNT
};
/*
* Structure containing token type and value. Used in `json_walk()` and
* `json_scanf()` with the format specifier `%T`.
*/
struct json_token {
const char *ptr; /* Points to the beginning of the value */
int len; /* Value length */
enum json_token_type type; /* Type of the token, possible values are above */
};
#define JSON_INVALID_TOKEN \
{ 0, 0, JSON_TYPE_INVALID }
/* Error codes */
#define JSON_STRING_INVALID -1
#define JSON_STRING_INCOMPLETE -2
/*
* Callback-based SAX-like API.
*
* Property name and length is given only if it's available: i.e. if current
* event is an object's property. In other cases, `name` is `NULL`. For
* example, name is never given:
* - For the first value in the JSON string;
* - For events JSON_TYPE_OBJECT_END and JSON_TYPE_ARRAY_END
*
* E.g. for the input `{ "foo": 123, "bar": [ 1, 2, { "baz": true } ] }`,
* the sequence of callback invocations will be as follows:
*
* - type: JSON_TYPE_OBJECT_START, name: NULL, path: "", value: NULL
* - type: JSON_TYPE_NUMBER, name: "foo", path: ".foo", value: "123"
* - type: JSON_TYPE_ARRAY_START, name: "bar", path: ".bar", value: NULL
* - type: JSON_TYPE_NUMBER, name: "0", path: ".bar[0]", value: "1"
* - type: JSON_TYPE_NUMBER, name: "1", path: ".bar[1]", value: "2"
* - type: JSON_TYPE_OBJECT_START, name: "2", path: ".bar[2]", value: NULL
* - type: JSON_TYPE_TRUE, name: "baz", path: ".bar[2].baz", value: "true"
* - type: JSON_TYPE_OBJECT_END, name: NULL, path: ".bar[2]", value: "{ \"baz\":
*true }"
* - type: JSON_TYPE_ARRAY_END, name: NULL, path: ".bar", value: "[ 1, 2, {
*\"baz\": true } ]"
* - type: JSON_TYPE_OBJECT_END, name: NULL, path: "", value: "{ \"foo\": 123,
*\"bar\": [ 1, 2, { \"baz\": true } ] }"
*/
typedef void (*json_walk_callback_t)(void *callback_data, const char *name,
size_t name_len, const char *path,
const struct json_token *token);
/*
* Parse `json_string`, invoking `callback` in a way similar to SAX parsers;
* see `json_walk_callback_t`.
* Return number of processed bytes, or a negative error code.
*/
int json_walk(const char *json_string, int json_string_length,
json_walk_callback_t callback, void *callback_data);
/*
* JSON generation API.
* struct json_out abstracts output, allowing alternative printing plugins.
*/
struct json_out {
int (*printer)(struct json_out *, const char *str, size_t len);
union {
struct {
char *buf;
size_t size;
size_t len;
} buf;
void *data;
FILE *fp;
} u;
};
extern int json_printer_buf(struct json_out *, const char *, size_t);
extern int json_printer_file(struct json_out *, const char *, size_t);
#define JSON_OUT_BUF(buf, len) \
{ \
json_printer_buf, { \
{ buf, len, 0 } \
} \
}
#define JSON_OUT_FILE(fp) \
{ \
json_printer_file, { \
{ (char *) fp, 0, 0 } \
} \
}
typedef int (*json_printf_callback_t)(struct json_out *, va_list *ap);
/*
* Generate formatted output into a given sting buffer.
* This is a superset of printf() function, with extra format specifiers:
* - `%B` print json boolean, `true` or `false`. Accepts an `int`.
* - `%Q` print quoted escaped string or `null`. Accepts a `const char *`.
* - `%.*Q` same as `%Q`, but with length. Accepts `int`, `const char *`
* - `%V` print quoted base64-encoded string. Accepts a `const char *`, `int`.
* - `%H` print quoted hex-encoded string. Accepts a `int`, `const char *`.
* - `%M` invokes a json_printf_callback_t function. That callback function
* can consume more parameters.
*
* Return number of bytes printed. If the return value is bigger then the
* supplied buffer, that is an indicator of overflow. In the overflow case,
* overflown bytes are not printed.
*/
int json_printf(struct json_out *, const char *fmt, ...);
int json_vprintf(struct json_out *, const char *fmt, va_list ap);
/*
* Same as json_printf, but prints to a file.
* File is created if does not exist. File is truncated if already exists.
*/
int json_fprintf(const char *file_name, const char *fmt, ...);
int json_vfprintf(const char *file_name, const char *fmt, va_list ap);
/*
* Helper %M callback that prints contiguous C arrays.
* Consumes void *array_ptr, size_t array_size, size_t elem_size, char *fmt
* Return number of bytes printed.
*/
int json_printf_array(struct json_out *, va_list *ap);
/*
* Scan JSON string `str`, performing scanf-like conversions according to `fmt`.
* This is a `scanf()` - like function, with following differences:
*
* 1. Object keys in the format string may be not quoted, e.g. "{key: %d}"
* 2. Order of keys in an object is irrelevant.
* 3. Several extra format specifiers are supported:
* - %B: consumes `int *` (or 'char *', if sizeof(bool) == sizeof(char)),
* expects boolean `true` or `false`.
* - %Q: consumes `char **`, expects quoted, JSON-encoded string. Scanned
* string is malloc-ed, caller must free() the string.
* - %V: consumes `char **`, `int *`. Expects base64-encoded string.
* Result string is base64-decoded, malloced and NUL-terminated.
* The length of result string is stored in `int *` placeholder.
* Caller must free() the result.
* - %H: consumes `int *`, `char **`.
* Expects a hex-encoded string, e.g. "fa014f".
* Result string is hex-decoded, malloced and NUL-terminated.
* The length of the result string is stored in `int *` placeholder.
* Caller must free() the result.
* - %M: consumes custom scanning function pointer and
* `void *user_data` parameter - see json_scanner_t definition.
* - %T: consumes `struct json_token *`, fills it out with matched token.
*
* Return number of elements successfully scanned & converted.
* Negative number means scan error.
*/
int json_scanf(const char *str, int str_len, const char *fmt, ...);
int json_vscanf(const char *str, int str_len, const char *fmt, va_list ap);
/* json_scanf's %M handler */
typedef void (*json_scanner_t)(const char *str, int len, void *user_data);
/*
* Helper function to scan array item with given path and index.
* Fills `token` with the matched JSON token.
* Return 0 if no array element found, otherwise non-0.
*/
int json_scanf_array_elem(const char *s, int len, const char *path, int index,
struct json_token *token);
/*
* Unescape JSON-encoded string src,slen into dst, dlen.
* src and dst may overlap.
* If destination buffer is too small (or zero-length), result string is not
* written but the length is counted nevertheless (similar to snprintf).
* Return the length of unescaped string in bytes.
*/
int json_unescape(const char *src, int slen, char *dst, int dlen);
/*
* Escape a string `str`, `str_len` into the printer `out`.
* Return the number of bytes printed.
*/
int json_escape(struct json_out *out, const char *str, size_t str_len);
/*
* Read the whole file in memory.
* Return malloc-ed file content, or NULL on error. The caller must free().
*/
char *json_fread(const char *file_name);
/*
* Update given JSON string `s,len` by changing the value at given `json_path`.
* The result is saved to `out`. If `json_fmt` == NULL, that deletes the key.
* If path is not present, missing keys are added. Array path without an
* index pushes a value to the end of an array.
* Return 1 if the string was changed, 0 otherwise.
*
* Example: s is a JSON string { "a": 1, "b": [ 2 ] }
* json_setf(s, len, out, ".a", "7"); // { "a": 7, "b": [ 2 ] }
* json_setf(s, len, out, ".b", "7"); // { "a": 1, "b": 7 }
* json_setf(s, len, out, ".b[]", "7"); // { "a": 1, "b": [ 2,7 ] }
* json_setf(s, len, out, ".b", NULL); // { "a": 1 }
*/
int json_setf(const char *s, int len, struct json_out *out,
const char *json_path, const char *json_fmt, ...);
int json_vsetf(const char *s, int len, struct json_out *out,
const char *json_path, const char *json_fmt, va_list ap);
/*
* Pretty-print JSON string `s,len` into `out`.
* Return number of processed bytes in `s`.
*/
int json_prettify(const char *s, int len, struct json_out *out);
/*
* Prettify JSON file `file_name`.
* Return number of processed bytes, or negative number of error.
* On error, file content is not modified.
*/
int json_prettify_file(const char *file_name);
/*
* Iterate over an object at given JSON `path`.
* On each iteration, fill the `key` and `val` tokens. It is OK to pass NULL
* for `key`, or `val`, in which case they won't be populated.
* Return an opaque value suitable for the next iteration, or NULL when done.
*
* Example:
*
* ```c
* void *h = NULL;
* struct json_token key, val;
* while ((h = json_next_key(s, len, h, ".foo", &key, &val)) != NULL) {
* printf("[%.*s] -> [%.*s]\n", key.len, key.ptr, val.len, val.ptr);
* }
* ```
*/
void *json_next_key(const char *s, int len, void *handle, const char *path,
struct json_token *key, struct json_token *val);
/*
* Iterate over an array at given JSON `path`.
* Similar to `json_next_key`, but fills array index `idx` instead of `key`.
*/
void *json_next_elem(const char *s, int len, void *handle, const char *path,
int *idx, struct json_token *val);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* CS_FROZEN_FROZEN_H_ */

18
unittest/main.c Normal file

@ -0,0 +1,18 @@
#include "test.h"
int test_failures=0;
int assert_count=0;
uint32_t mqtt_pub_count;
uint32_t mqtt_sub_count;
int main() {
test_buttons();
if (test_failures) {
LOG(LL_ERROR, ("%d test failures", test_failures));
return -1;
}
LOG(LL_INFO, ("All tests passed, %d assertions OK", assert_count));
return 0;
}

16
unittest/mgos.h Normal file

@ -0,0 +1,16 @@
#ifndef __MGOS_H
#define __MGOS_H
#include <stdint.h>
#include <stdbool.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "mgos_mock.h"
#include "mgos_gpio.h"
#include "mgos_net.h"
#include "mgos_mqtt.h"
#endif // __MGOS_H

5
unittest/mgos_config.c Normal file

@ -0,0 +1,5 @@
#include "mgos_config.h"
const char *mgos_sys_config_get_device_id() {
return "esp8266_ABCDEF";
}

6
unittest/mgos_config.h Normal file

@ -0,0 +1,6 @@
#ifndef __MGOS_CONFIG_H
#define __MGOS_CONFIG_H
const char *mgos_sys_config_get_device_id();
#endif // __MGOS_CONFIG_H

31
unittest/mgos_gpio.c Normal file

@ -0,0 +1,31 @@
#include "mgos.h"
#include "mgos_gpio.h"
static mgos_gpio_int_handler_f s_handler_cb;
static void *s_handler_cb_arg;
bool mgos_gpio_set_mode(int pin, enum mgos_gpio_mode mode) {
LOG(LL_INFO, ("Setting pin=%d to mode=%d", pin, mode));
return true;
}
void mgos_gpio_write(int pin, bool level) {
LOG(LL_INFO, ("Setting pin=%d to %s", pin, level?"HIGH":"LOW"));
}
bool mgos_gpio_set_button_handler(int pin, enum mgos_gpio_pull_type pull_type, enum mgos_gpio_int_mode int_mode, int debounce_ms, mgos_gpio_int_handler_f cb, void *arg) {
s_handler_cb = cb;
s_handler_cb_arg = arg;
return true;
(void) debounce_ms;
(void) int_mode;
(void) pull_type;
(void) pin;
}
void mgos_gpio_inject(int pin) {
if (s_handler_cb)
s_handler_cb(pin, s_handler_cb_arg);
}

39
unittest/mgos_gpio.h Normal file

@ -0,0 +1,39 @@
#ifndef __MGOS_GPIO_H
#define __MGOS_GPIO_H
#include "mgos.h"
enum mgos_gpio_mode {
MGOS_GPIO_MODE_INPUT = 0, /* input mode */
MGOS_GPIO_MODE_OUTPUT = 1 /* output mode */
};
enum mgos_gpio_pull_type {
MGOS_GPIO_PULL_NONE = 0,
MGOS_GPIO_PULL_UP = 1, /* pin is pilled to the high voltage */
MGOS_GPIO_PULL_DOWN = 2 /* pin is pulled to the low voltage */
};
enum mgos_gpio_int_mode {
MGOS_GPIO_INT_NONE = 0,
MGOS_GPIO_INT_EDGE_POS = 1, /* positive edge */
MGOS_GPIO_INT_EDGE_NEG = 2, /* negative edge */
MGOS_GPIO_INT_EDGE_ANY = 3, /* any edge - positive or negative */
MGOS_GPIO_INT_LEVEL_HI = 4, /* high voltage level */
MGOS_GPIO_INT_LEVEL_LO = 5 /* low voltage level */
};
typedef void (*mgos_gpio_int_handler_f)(int pin, void *arg);
bool mgos_gpio_set_mode(int pin, enum mgos_gpio_mode mode);
void mgos_gpio_write(int pin, bool level);
bool mgos_gpio_set_button_handler(int pin, enum mgos_gpio_pull_type pull_type,
enum mgos_gpio_int_mode int_mode,
int debounce_ms, mgos_gpio_int_handler_f cb,
void *arg);
void mgos_gpio_inject(int pin);
#endif // __MGOS_GPIO_H

75
unittest/mgos_mock.c Normal file

@ -0,0 +1,75 @@
/* Some functions mocked from MGOS, so we can run unit tests standalone.
*/
#include "mgos_mock.h"
int _mgos_timers = 0;
int log_print_prefix(enum cs_log_level l, const char *func, const char *file) {
char ll_str[6];
switch(l) {
case LL_ERROR:
strncpy(ll_str, "ERROR", sizeof(ll_str));
break;
case LL_WARN:
strncpy(ll_str, "WARN", sizeof(ll_str));
break;
case LL_INFO:
strncpy(ll_str, "INFO", sizeof(ll_str));
break;
case LL_DEBUG:
strncpy(ll_str, "DEBUG", sizeof(ll_str));
break;
case LL_VERBOSE_DEBUG:
strncpy(ll_str, "VERB", sizeof(ll_str));
break;
default: // LL_NONE
return 0;
}
printf ("%-5s %-15s %-40s| ", ll_str, file, func);
return 1;
}
mgos_timer_id mgos_set_timer(int msecs, int flags, timer_callback cb, void *cb_arg) {
_mgos_timers++;
LOG(LL_INFO, ("Installing timer -- %d timers currently installed", _mgos_timers));
(void) msecs;
(void) flags;
(void) cb;
(void) cb_arg;
return _mgos_timers;
}
void mgos_clear_timer(mgos_timer_id id) {
_mgos_timers--;
LOG(LL_INFO, ("Clearing timer -- %d timers currently installed", _mgos_timers));
(void) id;
return;
}
double mg_time() {
return (float) time(NULL);
}
double mgos_uptime() {
return (double) time(NULL);
}
char *mgos_sys_ro_vars_get_mac_address() {
return "00:11:22:33:44:55";
}
char *mgos_sys_ro_vars_get_arch() {
return "esp32";
}
bool mgos_net_get_ip_info(enum mgos_net_if_type if_type, int if_instance, struct mgos_net_ip_info *ip_info) {
return true;
}
void mgos_net_ip_to_str(const struct sockaddr_in *sin, char *out) {
return;
}

48
unittest/mgos_mock.h Normal file

@ -0,0 +1,48 @@
#ifndef __MGOS_MOCK_H
#define __MGOS_MOCK_H
/* Some functions mocked from MGOS, so we can run unit tests standalone.
*/
#include "mgos.h"
#include <time.h>
#include "mongoose/mongoose.h"
#define MGOS_APP "unittest"
// mgos_log
enum cs_log_level {
LL_NONE = -1,
LL_ERROR = 0,
LL_WARN = 1,
LL_INFO = 2,
LL_DEBUG = 3,
LL_VERBOSE_DEBUG = 4,
_LL_MIN = -2,
_LL_MAX = 5,
};
int log_print_prefix(enum cs_log_level l, const char *func, const char *file);
#define LOG(l, x) \
do { \
if (log_print_prefix(l, __func__, __FILE__)) printf x; \
printf("\r\n"); \
} while (0)
// mgos_timer
#define MGOS_TIMER_REPEAT 1
typedef uintptr_t mgos_timer_id;
typedef void (*timer_callback)(void *param);
mgos_timer_id mgos_set_timer(int msecs, int flags, timer_callback cb, void *cb_arg);
void mgos_clear_timer(mgos_timer_id id);
double mgos_uptime();
char *mgos_sys_ro_vars_get_mac_address();
char *mgos_sys_ro_vars_get_arch();
#endif // __MGOS_MOCK_H

35
unittest/mgos_mqtt.c Normal file

@ -0,0 +1,35 @@
#include "mgos.h"
#include "mgos_mqtt.h"
uint32_t mqtt_pub_count=0;
uint32_t mqtt_sub_count=0;
static sub_handler_t s_handler;
static void *s_handler_ud;
static mg_event_handler_t s_global_handler;
static void *s_global_handler_ud;
void mgos_mqtt_pub(char *t, char *m, int m_len, int flags, bool persist) {
LOG(LL_INFO, ("Sending topic='%s' msg='%s' persist=%s", t, m, persist?"ON":"OFF"));
mqtt_pub_count++;
}
void mgos_mqtt_sub(char *t, sub_handler_t cb, void *ud) {
LOG(LL_INFO, ("Subscribing to topic='%s'", t));
s_handler = cb;
s_handler_ud = ud;
}
void mgos_mqtt_inject(char *topic, char *msg) {
LOG(LL_INFO, ("Injecting topic='%s' msg='%s'", topic, msg));
mqtt_sub_count++;
if (s_handler)
s_handler(NULL, topic, strlen(topic), msg, strlen(msg), s_handler_ud);
}
void mgos_mqtt_add_global_handler(mqtt_event_handler_t handler, void *ud) {
s_global_handler = handler;
s_global_handler_ud = ud;
}

23
unittest/mgos_mqtt.h Normal file

@ -0,0 +1,23 @@
#ifndef __MGOS_MQTT_H
#define __MGOS_MQTT_H
#include "mgos.h"
#include "mongoose/mongoose.h"
struct mg_connection;
typedef void (*sub_handler_t)(struct mg_connection *nc, const char *topic,
int topic_len, const char *msg, int msg_len,
void *ud);
typedef void (*mqtt_event_handler_t)(struct mg_connection *nc, int ev,
void *ev_data, void *user_data);
void mgos_mqtt_pub(char *t, char *m, int m_len, int flags, bool persist);
void mgos_mqtt_sub(char *t, sub_handler_t cb, void *ud);
void mgos_mqtt_inject(char *topic, char *msg);
void mgos_mqtt_add_global_handler(mqtt_event_handler_t handler, void *ud);
#endif // __MGOS_MQTT_H

32
unittest/mgos_net.h Normal file

@ -0,0 +1,32 @@
#ifndef __MGOS_NET_H
#define __MGOS_NET_H
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#define MGOS_NET_IF_WIFI_STA 0
#define MGOS_NET_IF_WIFI_AP 1
enum mgos_net_if_type {
MGOS_NET_IF_TYPE_WIFI,
MGOS_NET_IF_TYPE_ETHERNET,
MGOS_NET_IF_TYPE_PPP,
/* This is a sentinel in case all networking interface types are disabled. */
MGOS_NET_IF_MAX,
};
struct mgos_net_ip_info {
struct sockaddr_in ip;
struct sockaddr_in netmask;
struct sockaddr_in gw;
};
bool mgos_net_get_ip_info(enum mgos_net_if_type if_type, int if_instance,
struct mgos_net_ip_info *ip_info);
void mgos_net_ip_to_str(const struct sockaddr_in *sin, char *out);
#endif // __MGOS_NET_H

6124
unittest/mongoose/mongoose.h Normal file

File diff suppressed because it is too large Load Diff

26
unittest/test.h Normal file

@ -0,0 +1,26 @@
#ifndef __TEST_H
#define __TEST_H
#include <stdlib.h>
#include <string.h>
#include "frozen/frozen.h"
#include "mgos_mock.h"
#include "main.h"
extern int test_failures;
extern int assert_count;
#define ASSERT(expr, errstr) \
do { \
if (!(expr)) { \
LOG(LL_ERROR, ("ASSERT FAIL: "errstr)); \
test_failures++; \
} \
assert_count++; \
} while (0)
int test_buttons(void);
#endif // __TEST_H

9
unittest/test_buttons.c Normal file

@ -0,0 +1,9 @@
#include "test.h"
int test_buttons() {
channel_init("testdata/testconfig0.json");
channel_init("testdata/testconfig1.json");
return 0;
}

9
unittest/testdata/testconfig0.json vendored Normal file

@ -0,0 +1,9 @@
{
"name": "3 Gang",
"status_led": 0,
"channels": [
{ "led": 2, "relay": 15, "button": 5 },
{ "led": 14, "relay": 4, "button": 3 },
{ "led": 16, "relay": 13, "button": 12 }
]
}

9
unittest/testdata/testconfig1.json vendored Normal file

@ -0,0 +1,9 @@
{
"name": "3 Gang, No LEDs",
"status_led": -1,
"channels": [
{ "led": -1, "relay": 15, "button": 5 },
{ "led": -1, "relay": 4, "button": 3 },
{ "led": -1, "relay": 13, "button": 12 }
]
}