From: Alan T. DeKok Date: Wed, 19 Aug 2015 09:54:25 +0000 (-0400) Subject: Added unit test framework for maps X-Git-Tag: release_3_0_10~218 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=89b2f045650303fddf2dc1b464fd35c9fced3ccc;p=thirdparty%2Ffreeradius-server.git Added unit test framework for maps Which are run after the unit tests, and before xlat / keyword tests. These tests are for parsing ONLY. They don't verify that the maps DO anything. --- diff --git a/src/tests/all.mk b/src/tests/all.mk index 99d131a1994..d87a22e0820 100644 --- a/src/tests/all.mk +++ b/src/tests/all.mk @@ -1,4 +1,4 @@ -SUBMAKEFILES := rbmonkey.mk unit/all.mk xlat/all.mk keywords/all.mk auth/all.mk modules/all.mk +SUBMAKEFILES := rbmonkey.mk unit/all.mk map/all.mk xlat/all.mk keywords/all.mk auth/all.mk modules/all.mk # # Include all of the autoconf definitions into the Make variable space diff --git a/src/tests/bob b/src/tests/bob new file mode 100644 index 00000000000..f9398ad87ef --- /dev/null +++ b/src/tests/bob @@ -0,0 +1,2 @@ +User-Name = "bob" +User-Password = "bob" diff --git a/src/tests/keywords/all.mk b/src/tests/keywords/all.mk index dddf78699c4..369bb7b7d70 100644 --- a/src/tests/keywords/all.mk +++ b/src/tests/keywords/all.mk @@ -116,7 +116,7 @@ TESTS.KEYWORDS_FILES := $(addprefix $(BUILD_DIR)/tests/keywords/,$(KEYWORD_FILES # tests.keywords: $(TESTS.KEYWORDS_FILES) -$(TESTS.KEYWORDS_FILES): $(TESTS.XLAT_FILES) +$(TESTS.KEYWORDS_FILES): $(TESTS.XLAT_FILES) $(TESTS.MAP_FILES) .PHONY: clean.tests.keywords clean.tests.keywords: diff --git a/src/tests/keywords/count-error b/src/tests/keywords/count-error new file mode 100644 index 00000000000..f0723cb56b7 --- /dev/null +++ b/src/tests/keywords/count-error @@ -0,0 +1,11 @@ +update control { + Cleartext-Password := 'hello' +} + +update reply { + Filter-Id := "filter" +} + +update request { + Tmp-String-0 := &reply:Filter-Id[#] # ERROR +} diff --git a/src/tests/keywords/foreach-break-4 b/src/tests/keywords/foreach-break-4 new file mode 100644 index 00000000000..037af8ead44 --- /dev/null +++ b/src/tests/keywords/foreach-break-4 @@ -0,0 +1,44 @@ +# +# PRE: foreach foreach-break-3 +# + +update request { + Calling-Station-Id := "8" +} + +update control { + &Calling-Station-Id := "0" + &Calling-Station-Id += "1" + &Calling-Station-Id += "2" + &Calling-Station-Id += "3" + &Calling-Station-Id += "4" + &Calling-Station-Id += "5" + &Calling-Station-Id += "6" + &Calling-Station-Id += "7" + &Calling-Station-Id += "8" + &Calling-Station-Id += "9" + &Calling-Station-Id += "a" + &Calling-Station-Id += "b" + &Calling-Station-Id += "c" + &Calling-Station-Id += "d" + &Calling-Station-Id += "e" + &Calling-Station-Id += "f" + &Calling-Station-Id += "g" +} + +foreach &control:Calling-Station-Id { + if (&request:Calling-Station-Id == "%{Foreach-Variable-0}") { + update reply { + Filter-Id := "filter" + } + + break + } + elsif ("%{Foreach-Variable-0}" == '9') { + update reply { + Filter-Id := "fail-9" + } + + reject + } +} diff --git a/src/tests/keywords/switch-escape b/src/tests/keywords/switch-escape new file mode 100644 index 00000000000..50d9fdf7b49 --- /dev/null +++ b/src/tests/keywords/switch-escape @@ -0,0 +1,43 @@ +update request { + &Tmp-String-0 := 'foo' +} + +switch "%{tolower:%{request:Tmp-String-0}}" { + case 'foo' { + update reply { + Filter-Id := "filter" + } + } + + case '' { + update reply { + Filter-Id += "fail-empty-1" + } + } + + case { + update reply { + Filter-Id += "fail-default-1" + } + } +} + +switch "%{request:Tmp-String-0}" { + case 'foo' { + update reply { + Filter-Id := "filter" + } + } + + case '' { + update reply { + Filter-Id += "fail-empty-2" + } + } + + case { + update reply { + Filter-Id += "fail-default-2" + } + } +} diff --git a/src/tests/keywords/update-delete b/src/tests/keywords/update-delete new file mode 100644 index 00000000000..a5c2d5a1771 --- /dev/null +++ b/src/tests/keywords/update-delete @@ -0,0 +1,40 @@ +# +# PRE: update +# +# Remove all attributes in a list +# +update { + control:Cleartext-Password := 'hello' + reply:Filter-Id := 'filter' +} + +update request { + Tmp-String-0 := 'foobarbaz' + Tmp-Integer-0 := 123456789 + Tmp-IP-Address-0 := 192.0.2.1 +} + +if ((Tmp-String-0 != 'foobarbaz') || (Tmp-Integer-0 != 123456789) || (Tmp-IP-Address-0 != 192.0.2.1)) { + update { + reply:Filter-Id := 'fail' + } +} + +# Remove all attributes in the control list +update { + request: !* ANY +} + +# All attributes should now of been removed +if ((Tmp-String-0 && (Tmp-String-0 == 'foobarbaz')) || \ + (Tmp-Integer-0 && (Tmp-Integer-0 == 123456789)) || \ + (Tmp-IP-Address-0 && (Tmp-IP-Address-0 == 192.0.2.1))) { + update { + reply:Filter-Id := 'fail' + } +} + +# This will of been removed too +update request { + User-Password := 'hello' +} diff --git a/src/tests/keywords/virtual b/src/tests/keywords/virtual new file mode 100644 index 00000000000..d6dbe32bb03 --- /dev/null +++ b/src/tests/keywords/virtual @@ -0,0 +1,12 @@ +# +# PRE: update if +# +update control { + Cleartext-Password := 'hello' +} + +if (request:Packet-Type == Access-Request) { + update reply { + Filter-Id := "filter" + } +} diff --git a/src/tests/map/all.mk b/src/tests/map/all.mk new file mode 100644 index 00000000000..fedd29d9100 --- /dev/null +++ b/src/tests/map/all.mk @@ -0,0 +1 @@ +SUBMAKEFILES := map_unit.mk map_tests.mk diff --git a/src/tests/map/base b/src/tests/map/base new file mode 100644 index 00000000000..633c32a6888 --- /dev/null +++ b/src/tests/map/base @@ -0,0 +1,6 @@ +update request { + Filter-Id := "filter" + User-Name := "blah" + + &reply:Filter-Id += &request:Filter-Id[*] +} diff --git a/src/tests/map/base.out b/src/tests/map/base.out new file mode 100644 index 00000000000..34c519b7cd4 --- /dev/null +++ b/src/tests/map/base.out @@ -0,0 +1,5 @@ +update request { + &Filter-Id := "filter" + &User-Name := "blah" + &reply:Filter-Id += &Filter-Id[*] +} diff --git a/src/tests/map/map_tests.mk b/src/tests/map/map_tests.mk new file mode 100644 index 00000000000..886e6b2fb5c --- /dev/null +++ b/src/tests/map/map_tests.mk @@ -0,0 +1,49 @@ +MAP_TESTS := $(patsubst $(top_srcdir)/src/tests/map/%,%,$(filter-out %.conf %.md %.attrs %.c %.mk %~ %.rej %.out,$(wildcard $(top_srcdir)/src/tests/map/*))) +MAP_OUTPUT := $(addsuffix .out,$(addprefix $(BUILD_DIR)/tests/map/,$(MAP_TESTS))) +MAP_UNIT := $(BUILD_DIR)/bin/local/map_unit + +.PHONY: $(BUILD_DIR)/tests/map/ +$(BUILD_DIR)/tests/map/: + @mkdir -p $@ + +# +# Re-run the tests if the test program changes +# +# Create the output directory before the files +# +$(MAP_OUTPUT): $(MAP_UNIT) | $(BUILD_DIR)/tests/map/ + +# +# Re-run the tests if the input file changes +# +$(BUILD_DIR)/tests/map/%.out: $(top_srcdir)/src/tests/map/% + @echo MAP_TEST $(notdir $<) + @if ! $(MAP_UNIT) -d $(top_srcdir)/raddb -D $(top_srcdir)/share $< > $@ 2>&1; then \ + if ! grep ERROR $< 2>&1 > /dev/null; then \ + cat $@; \ + echo "# $@"; \ + echo FAILED: "$(MAP_UNIT) -d $(top_srcdir)/raddb -D $(top_srcdir)/share $<"; \ + exit 1; \ + fi; \ + FOUND=$$(grep $< $@ | head -1 | sed 's,^.*$(top_srcdir),,;s/:.*//;s/.*\[//;s/\].*//'); \ + EXPECTED=$$(grep -n ERROR $< | sed 's/:.*//'); \ + if [ "$$EXPECTED" != "$$FOUND" ]; then \ + cat $@; \ + echo "# $@"; \ + echo "E $$EXPECTED F $$FOUND"; \ + echo UNEXPECTED ERROR: "$(MAP_UNIT) -d $(top_srcdir)/raddb -D $(top_srcdir)/share $<"; \ + exit 1; \ + fi; \ + else \ + if ! diff $<.out $@; then \ + echo FAILED: " diff $<.out $@"; \ + echo FAILED: "$(MAP_UNIT) -d $(top_srcdir)/raddb -D $(top_srcdir)/share $<"; \ + exit 1; \ + fi; \ + fi + +TESTS.MAP_FILES := $(MAP_OUTPUT) + +$(TESTS.MAP_FILES): $(TESTS.UNIT_FILES) + +tests.map: $(MAP_OUTPUT) diff --git a/src/tests/map/map_unit.c b/src/tests/map/map_unit.c new file mode 100644 index 00000000000..5c0b7a479ea --- /dev/null +++ b/src/tests/map/map_unit.c @@ -0,0 +1,197 @@ +/* + * radattr.c Map debugging tool. + * + * Version: $Id$ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + * + * Copyright 2015 Alan DeKok + */ + +RCSID("$Id$") + +#include + +#include +#include +#include + +#include + +#ifdef HAVE_GETOPT_H +# include +#endif + +#include + +#include +extern log_lvl_t rad_debug_lvl; + +#include +pid_t rad_fork(void); +pid_t rad_waitpid(pid_t pid, int *status); + +pid_t rad_fork(void) +{ + return fork(); +} + +pid_t rad_waitpid(pid_t pid, int *status) +{ + return waitpid(pid, status, 0); +} + + +static void NEVER_RETURNS usage(void) +{ + fprintf(stderr, "usage: map_unit [OPTS] filename ...\n"); + fprintf(stderr, " -d Set user dictionary directory (defaults to " RADDBDIR ").\n"); + fprintf(stderr, " -D Set main dictionary directory (defaults to " DICTDIR ").\n"); + fprintf(stderr, " -O Set output directory\n"); + fprintf(stderr, " -x Debugging mode.\n"); + fprintf(stderr, " -M Show program version information.\n"); + + exit(1); +} + +static int process_file(char const *filename) +{ + int rcode; + char const *name1, *name2; + CONF_SECTION *cs, *main_cs; + vp_map_t *head, *map; + char buffer[8192]; + + main_cs = cf_section_alloc(NULL, "main", NULL); + if (cf_file_read(main_cs, filename) < 0) { + fprintf(stderr, "map_unit: Failed parsing %s\n", + filename); + exit(1); + } + + /* + * Always has to be an "update" section. + */ + cs = cf_section_sub_find(main_cs, "update"); + if (!cs) { + talloc_free(main_cs); + return -1; + } + + /* + * Convert the update section to a list of maps. + */ + rcode = map_afrom_cs(&head, cs, PAIR_LIST_REQUEST, PAIR_LIST_REQUEST, modcall_fixup_update, NULL, 128); + if (rcode < 0) return -1; /* message already printed */ + if (!head) { + cf_log_err_cs(cs, "'update' sections cannot be empty"); + return -1; + } + + buffer[0] = '\t'; + + name1 = cf_section_name1(cs); + name2 = cf_section_name2(cs); + + /* + * And print it all out. + */ + if (!name2) { + printf("%s {\n", name1); + } else { + printf("%s %s {\n", name1, name2); + } + + for (map = head; map != NULL; map = map->next) { + map_prints(buffer + 1, sizeof(buffer) - 1, map); + puts(buffer); + } + printf("}\n"); + + talloc_free(main_cs); + return 0; +} + +int main(int argc, char *argv[]) +{ + int c, rcode = 0; + bool report = false; + char const *radius_dir = RADDBDIR; + char const *dict_dir = DICTDIR; + + cf_new_escape = true; /* fix the tests */ + +#ifndef NDEBUG + if (fr_fault_setup(getenv("PANIC_ACTION"), argv[0]) < 0) { + fr_perror("radattr"); + exit(EXIT_FAILURE); + } +#endif + + while ((c = getopt(argc, argv, "d:D:xMh")) != EOF) switch (c) { + case 'd': + radius_dir = optarg; + break; + case 'D': + dict_dir = optarg; + break; + case 'x': + fr_debug_lvl++; + rad_debug_lvl = fr_debug_lvl; + break; + case 'M': + report = true; + break; + case 'h': + default: + usage(); + } + argc -= (optind - 1); + argv += (optind - 1); + + /* + * Mismatch between the binary and the libraries it depends on + */ + if (fr_check_lib_magic(RADIUSD_MAGIC_NUMBER) < 0) { + fr_perror("radattr"); + return 1; + } + + if (dict_init(dict_dir, RADIUS_DICTIONARY) < 0) { + fr_perror("radattr"); + return 1; + } + + if (dict_read(radius_dir, RADIUS_DICTIONARY) == -1) { + fr_perror("radattr"); + return 1; + } + + if (argc < 2) { + rcode = process_file("-"); + + } else { + rcode = process_file(argv[1]); + } + + if (report) { + dict_free(); + fr_log_talloc_report(NULL); + } + + if (rcode < 0) rcode = 1; /* internal to Unix process return code */ + + return rcode; +} diff --git a/src/tests/map/map_unit.mk b/src/tests/map/map_unit.mk new file mode 100644 index 00000000000..88d319ba810 --- /dev/null +++ b/src/tests/map/map_unit.mk @@ -0,0 +1,5 @@ +TARGET := map_unit +SOURCES := map_unit.c ${top_srcdir}/src/main/modcall.c + +TGT_PREREQS := libfreeradius-server.a libfreeradius-radius.a +TGT_LDLIBS := $(LIBS) diff --git a/src/tests/peap-client-mschapv2.conf b/src/tests/peap-client-mschapv2.conf new file mode 100644 index 00000000000..1c609338c18 --- /dev/null +++ b/src/tests/peap-client-mschapv2.conf @@ -0,0 +1,18 @@ +# +# ./eapol_test -c peap-mschapv2.conf -s testing123 +# +network={ + ssid="example" + key_mgmt=WPA-EAP + eap=PEAP + identity="bob" + anonymous_identity="anonymous" + password="bob" + phase2="auth=MSCHAPV2" + phase1="peapver=0" + + ca_cert="../../raddb/certs/ca.pem" + client_cert="../../raddb/certs/client.crt" + private_key="../../raddb/certs/client.key" + private_key_passwd="whatever" +} diff --git a/src/tests/unit/foo b/src/tests/unit/foo new file mode 100644 index 00000000000..add4ab58d72 --- /dev/null +++ b/src/tests/unit/foo @@ -0,0 +1,17 @@ +# +# We can't look at the data here, because the encoded Tunnel-Password has a 2 byte +# random salt +# +#encode Tunnel-Password:0 := "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +#decode - +#data Tunnel-Password:0 = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + +# +# 1 octet for the tag. 2 octets for salt. One octet for encrypted length. +# 249 octets left for real data. +# +encode Tunnel-Password:0 = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx123456789ab" +#data x +decode - +data Tunnel-Password:0 = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx123456789" +fff