diff --git a/raster/r.describe/Makefile b/raster/r.describe/Makefile index ddd265e6d58..52ba3114f4d 100644 --- a/raster/r.describe/Makefile +++ b/raster/r.describe/Makefile @@ -2,7 +2,7 @@ MODULE_TOPDIR = ../.. PGM = r.describe -LIBES = $(RASTERLIB) $(GISLIB) +LIBES = $(RASTERLIB) $(GISLIB) $(PARSONLIB) DEPENDENCIES = $(RASTERDEP) $(GISDEP) include $(MODULE_TOPDIR)/include/Make/Module.make diff --git a/raster/r.describe/describe.c b/raster/r.describe/describe.c index 69b65aeec6e..11540d93267 100644 --- a/raster/r.describe/describe.c +++ b/raster/r.describe/describe.c @@ -18,10 +18,12 @@ #include #include #include + #include "local_proto.h" int describe(const char *name, int compact, char *no_data_str, int range, - int windowed, int nsteps, int as_int, int skip_nulls) + int windowed, int nsteps, int as_int, int skip_nulls, + enum OutputFormat format) { int fd; struct Cell_stats statf; @@ -127,20 +129,20 @@ int describe(const char *name, int compact, char *no_data_str, int range, if (range) { if (compact) compact_range_list(negmin, negmax, zero, posmin, posmax, null, - no_data_str, skip_nulls); + no_data_str, skip_nulls, format); else range_list(negmin, negmax, zero, posmin, posmax, null, no_data_str, - skip_nulls); + skip_nulls, format); } else { Rast_rewind_cell_stats(&statf); if (compact) compact_list(&statf, dmin, dmax, no_data_str, skip_nulls, map_type, - nsteps); + nsteps, format); else long_list(&statf, dmin, dmax, no_data_str, skip_nulls, map_type, - nsteps); + nsteps, format); Rast_free_cell_stats(&statf); } diff --git a/raster/r.describe/dumplist.c b/raster/r.describe/dumplist.c index 7191679463b..890e875d32e 100644 --- a/raster/r.describe/dumplist.c +++ b/raster/r.describe/dumplist.c @@ -18,43 +18,176 @@ #include #include #include +#include -static int show(CELL, CELL, int *, DCELL, DCELL, RASTER_MAP_TYPE, int); +#include "local_proto.h" + +static void initialize_json_object(JSON_Value **, JSON_Object **); +static void initialize_json_array(JSON_Value **, JSON_Array **); +static void append_category_ranges(JSON_Array *range_array, long min, long max); +static void output_pretty_json(JSON_Value *); +static int show(CELL, CELL, int *, DCELL, DCELL, RASTER_MAP_TYPE, int, + enum OutputFormat, JSON_Array *); + +void initialize_json_object(JSON_Value **root_value, JSON_Object **root_object) +{ + *root_value = json_value_init_object(); + if (*root_value == NULL) { + G_fatal_error(_("Failed to initialize JSON object. Out of memory?")); + } + *root_object = json_object(*root_value); +} + +void initialize_json_array(JSON_Value **root_value, JSON_Array **root_array) +{ + *root_value = json_value_init_array(); + if (*root_value == NULL) { + G_fatal_error(_("Failed to initialize JSON array. Out of memory?")); + } + *root_array = json_array(*root_value); +} + +void append_category_ranges(JSON_Array *range_array, long min, long max) +{ + JSON_Object *cat_object; + JSON_Value *cat_value; + initialize_json_object(&cat_value, &cat_object); + + json_object_set_number(cat_object, "min", min); + json_object_set_number(cat_object, "max", max); + + json_array_append_value(range_array, cat_value); +} + +void output_pretty_json(JSON_Value *root_value) +{ + char *serialized_string = json_serialize_to_string_pretty(root_value); + if (!serialized_string) { + json_value_free(root_value); + G_fatal_error(_("Failed to initialize pretty JSON string.")); + } + puts(serialized_string); + + json_free_serialized_string(serialized_string); + json_value_free(root_value); +} int long_list(struct Cell_stats *statf, DCELL dmin, DCELL dmax, char *no_data_str, int skip_nulls, RASTER_MAP_TYPE map_type, - int nsteps) + int nsteps, enum OutputFormat format) { CELL cat; long count; /* not used, but required by cell stats call */ + JSON_Value *root_value, *range_value; + JSON_Object *root_object; + JSON_Array *range_array; + + if (format == JSON) { + initialize_json_object(&root_value, &root_object); + initialize_json_array(&range_value, &range_array); + } Rast_get_stats_for_null_value(&count, statf); - if (count != 0 && !skip_nulls) - fprintf(stdout, "%s\n", no_data_str); + if (!skip_nulls) { + if (count != 0) { + switch (format) { + case PLAIN: + fprintf(stdout, "%s\n", no_data_str); + break; + case JSON: + json_object_set_boolean(root_object, "has_nulls", true); + break; + } + } + else { + if (format == JSON) { + json_object_set_boolean(root_object, "has_nulls", false); + } + } + } while (Rast_next_cell_stat(&cat, &count, statf)) { - if (map_type != CELL_TYPE) - fprintf(stdout, "%f-%f\n", - dmin + (double)(cat - 1) * (dmax - dmin) / nsteps, - dmin + (double)cat * (dmax - dmin) / nsteps); - else - fprintf(stdout, "%ld\n", (long)cat); + switch (format) { + case PLAIN: + if (map_type != CELL_TYPE) + fprintf(stdout, "%f-%f\n", + dmin + (double)(cat - 1) * (dmax - dmin) / nsteps, + dmin + (double)cat * (dmax - dmin) / nsteps); + else + fprintf(stdout, "%ld\n", (long)cat); + break; + case JSON: + if (map_type != CELL_TYPE) { + JSON_Object *cat_object; + JSON_Value *cat_value; + initialize_json_object(&cat_value, &cat_object); + + json_object_set_number(cat_object, "min", + dmin + (double)(cat - 1) * + (dmax - dmin) / nsteps); + json_object_set_number(cat_object, "max", + dmin + (double)cat * (dmax - dmin) / + nsteps); + + json_array_append_value(range_array, cat_value); + } + else { + json_array_append_number(range_array, (long)cat); + } + + break; + } } + + if (format == JSON) { + if (map_type != CELL_TYPE) { + json_object_set_value(root_object, "ranges", range_value); + } + else { + json_object_set_value(root_object, "values", range_value); + } + + output_pretty_json(root_value); + } + return (0); } int compact_list(struct Cell_stats *statf, DCELL dmin, DCELL dmax, char *no_data_str, int skip_nulls, RASTER_MAP_TYPE map_type, - int nsteps) + int nsteps, enum OutputFormat format) { CELL cat1, cat2, temp; int len; long count; /* not used, but required by cell stats call */ + JSON_Value *root_value, *range_value; + JSON_Object *root_object; + JSON_Array *range_array; + + if (format == JSON) { + initialize_json_object(&root_value, &root_object); + initialize_json_array(&range_value, &range_array); + } len = 0; Rast_get_stats_for_null_value(&count, statf); - if (count != 0 && !skip_nulls) - fprintf(stdout, "%s ", no_data_str); + if (!skip_nulls) { + if (count != 0) { + switch (format) { + case PLAIN: + fprintf(stdout, "%s ", no_data_str); + break; + case JSON: + json_object_set_boolean(root_object, "has_nulls", true); + break; + } + } + else { + if (format == JSON) { + json_object_set_boolean(root_object, "has_nulls", false); + } + } + } if (!Rast_next_cell_stat(&cat1, &count, statf)) /* map doesn't contain any non-null data */ @@ -63,94 +196,244 @@ int compact_list(struct Cell_stats *statf, DCELL dmin, DCELL dmax, cat2 = cat1; while (Rast_next_cell_stat(&temp, &count, statf)) { if (temp != cat2 + (CELL)1) { - show(cat1, cat2, &len, dmin, dmax, map_type, nsteps); + show(cat1, cat2, &len, dmin, dmax, map_type, nsteps, format, + range_array); cat1 = temp; } cat2 = temp; } - show(cat1, cat2, &len, dmin, dmax, map_type, nsteps); - fprintf(stdout, "\n"); + show(cat1, cat2, &len, dmin, dmax, map_type, nsteps, format, range_array); + + switch (format) { + case PLAIN: + fprintf(stdout, "\n"); + break; + case JSON: + json_object_set_value(root_object, "ranges", range_value); + output_pretty_json(root_value); + break; + } + return (0); } static int show(CELL low, CELL high, int *len, DCELL dmin, DCELL dmax, - RASTER_MAP_TYPE map_type, int nsteps) + RASTER_MAP_TYPE map_type, int nsteps, enum OutputFormat format, + JSON_Array *root_array) { char text[100]; char xlen; + JSON_Object *cat_object; + JSON_Value *cat_value; if (low + 1 == high) { - show(low, low, len, dmin, dmax, map_type, nsteps); - show(high, high, len, dmin, dmax, map_type, nsteps); + show(low, low, len, dmin, dmax, map_type, nsteps, format, root_array); + show(high, high, len, dmin, dmax, map_type, nsteps, format, root_array); return 0; } + if (format == JSON) + initialize_json_object(&cat_value, &cat_object); + if (map_type != CELL_TYPE) { - sprintf(text, "%f%s%f ", dmin + (low - 1) * (dmax - dmin) / nsteps, - dmin < 0 ? " thru " : "-", - dmin + high * (dmax - dmin) / nsteps); + switch (format) { + case PLAIN: + sprintf(text, "%f%s%f ", dmin + (low - 1) * (dmax - dmin) / nsteps, + dmin < 0 ? " thru " : "-", + dmin + high * (dmax - dmin) / nsteps); + break; + case JSON: + json_object_set_number(cat_object, "min", + dmin + (low - 1) * (dmax - dmin) / nsteps); + json_object_set_number(cat_object, "max", + dmin + high * (dmax - dmin) / nsteps); + break; + } } else { - if (low == high) - sprintf(text, "%ld ", (long)low); - else - sprintf(text, "%ld%s%ld ", (long)low, low < 0 ? " thru " : "-", - (long)high); + switch (format) { + case PLAIN: + if (low == high) + sprintf(text, "%ld ", (long)low); + else + sprintf(text, "%ld%s%ld ", (long)low, low < 0 ? " thru " : "-", + (long)high); + break; + case JSON: + json_object_set_number(cat_object, "min", (long)low); + json_object_set_number(cat_object, "max", (long)high); + break; + } } xlen = strlen(text); if (xlen + *len > 78) { - fprintf(stdout, "\n"); + if (format == PLAIN) + fprintf(stdout, "\n"); *len = 0; } - fprintf(stdout, "%s", text); + + switch (format) { + case PLAIN: + fprintf(stdout, "%s", text); + break; + case JSON: + json_array_append_value(root_array, cat_value); + break; + } + *len += xlen; return (0); } int compact_range_list(CELL negmin, CELL negmax, CELL zero, CELL posmin, CELL posmax, CELL null, char *no_data_str, - int skip_nulls) + int skip_nulls, enum OutputFormat format) { + JSON_Value *root_value, *range_value; + JSON_Object *root_object; + JSON_Array *range_array; + + if (format == JSON) { + initialize_json_object(&root_value, &root_object); + initialize_json_array(&range_value, &range_array); + } + if (negmin) { - fprintf(stdout, "%ld", (long)negmin); - if (negmin != negmax) - fprintf(stdout, " thru %ld", (long)negmax); - fprintf(stdout, "\n"); + switch (format) { + case PLAIN: + fprintf(stdout, "%ld", (long)negmin); + if (negmin != negmax) + fprintf(stdout, " thru %ld", (long)negmax); + fprintf(stdout, "\n"); + break; + case JSON: + append_category_ranges(range_array, (long)negmin, (long)negmax); + break; + } + } + if (zero) { + switch (format) { + case PLAIN: + fprintf(stdout, "0\n"); + break; + case JSON: + append_category_ranges(range_array, 0, 0); + break; + } } - if (zero) - fprintf(stdout, "0\n"); if (posmin) { - fprintf(stdout, "%ld", (long)posmin); - if (posmin != posmax) - fprintf(stdout, " thru %ld", (long)posmax); - fprintf(stdout, "\n"); + switch (format) { + case PLAIN: + fprintf(stdout, "%ld", (long)posmin); + if (posmin != posmax) + fprintf(stdout, " thru %ld", (long)posmax); + fprintf(stdout, "\n"); + break; + case JSON: + append_category_ranges(range_array, (long)posmin, (long)posmax); + break; + } } - if (null && !skip_nulls) - fprintf(stdout, "%s\n", no_data_str); + if (!skip_nulls) { + if (null) { + switch (format) { + case PLAIN: + fprintf(stdout, "%s\n", no_data_str); + break; + case JSON: + json_object_set_boolean(root_object, "has_nulls", true); + break; + } + } + else { + if (format == JSON) { + json_object_set_boolean(root_object, "has_nulls", false); + } + } + } + + if (format == JSON) { + json_object_set_value(root_object, "ranges", range_value); + output_pretty_json(root_value); + } return (0); } int range_list(CELL negmin, CELL negmax, CELL zero, CELL posmin, CELL posmax, - CELL null, char *no_data_str, int skip_nulls) + CELL null, char *no_data_str, int skip_nulls, + enum OutputFormat format) { + JSON_Value *root_value, *range_value; + JSON_Object *root_object; + JSON_Array *range_array; + + if (format == JSON) { + initialize_json_object(&root_value, &root_object); + initialize_json_array(&range_value, &range_array); + } + if (negmin) { - fprintf(stdout, "%ld\n", (long)negmin); - if (negmin != negmax) - fprintf(stdout, "%ld\n", (long)negmax); + switch (format) { + case PLAIN: + fprintf(stdout, "%ld\n", (long)negmin); + if (negmin != negmax) + fprintf(stdout, "%ld\n", (long)negmax); + break; + case JSON: + append_category_ranges(range_array, (long)negmin, (long)negmax); + break; + } } - if (zero) - fprintf(stdout, "0\n"); + + if (zero) { + switch (format) { + case PLAIN: + fprintf(stdout, "0\n"); + break; + case JSON: + append_category_ranges(range_array, 0, 0); + break; + } + } + if (posmin) { - fprintf(stdout, "%ld\n", (long)posmin); - if (posmin != posmax) - fprintf(stdout, "%ld\n", (long)posmax); + switch (format) { + case PLAIN: + fprintf(stdout, "%ld\n", (long)posmin); + if (posmin != posmax) + fprintf(stdout, "%ld\n", (long)posmax); + break; + case JSON: + append_category_ranges(range_array, (long)posmin, (long)posmax); + break; + } } - if (null && !skip_nulls) - fprintf(stdout, "%s\n", no_data_str); + if (!skip_nulls) { + if (null) { + switch (format) { + case PLAIN: + fprintf(stdout, "%s\n", no_data_str); + break; + case JSON: + json_object_set_boolean(root_object, "has_nulls", true); + break; + } + } + else { + if (format == JSON) { + json_object_set_boolean(root_object, "has_nulls", false); + } + } + } + + if (format == JSON) { + json_object_set_value(root_object, "ranges", range_value); + output_pretty_json(root_value); + } return (0); } diff --git a/raster/r.describe/local_proto.h b/raster/r.describe/local_proto.h index 530f7e0565b..ecf51473899 100644 --- a/raster/r.describe/local_proto.h +++ b/raster/r.describe/local_proto.h @@ -19,17 +19,23 @@ #define __R_DESC_LOCAL_PROTO_H__ #include +#include + +enum OutputFormat { PLAIN, JSON }; /* describe.c */ -int describe(const char *, int, char *, int, int, int, int, int); +int describe(const char *, int, char *, int, int, int, int, int, + enum OutputFormat); /* dumplist.c */ int long_list(struct Cell_stats *, DCELL, DCELL, char *, int, RASTER_MAP_TYPE, - int); + int, enum OutputFormat); int compact_list(struct Cell_stats *, DCELL, DCELL, char *, int, - RASTER_MAP_TYPE, int); -int compact_range_list(CELL, CELL, CELL, CELL, CELL, CELL, char *, int); -int range_list(CELL, CELL, CELL, CELL, CELL, CELL, char *, int); + RASTER_MAP_TYPE, int, enum OutputFormat); +int compact_range_list(CELL, CELL, CELL, CELL, CELL, CELL, char *, int, + enum OutputFormat); +int range_list(CELL, CELL, CELL, CELL, CELL, CELL, char *, int, + enum OutputFormat); /* main.c */ int main(int, char *[]); diff --git a/raster/r.describe/main.c b/raster/r.describe/main.c index 394a228871b..c4e204ba0cf 100644 --- a/raster/r.describe/main.c +++ b/raster/r.describe/main.c @@ -19,9 +19,10 @@ #include #include #include -#include "local_proto.h" #include +#include "local_proto.h" + int main(int argc, char *argv[]) { int as_int; @@ -42,7 +43,9 @@ int main(int argc, char *argv[]) struct Option *map; struct Option *nv; struct Option *nsteps; + struct Option *format; } option; + enum OutputFormat format; G_gisinit(argv[0]); @@ -66,6 +69,9 @@ int main(int argc, char *argv[]) option.nsteps->answer = "255"; option.nsteps->description = _("Number of quantization steps"); + option.format = G_define_standard_option(G_OPT_F_FORMAT); + option.format->guisection = _("Print"); + /*define the different flags */ flag.one = G_define_flag(); @@ -97,12 +103,19 @@ int main(int argc, char *argv[]) as_int = flag.i->answer; no_data_str = option.nv->answer; + if (strcmp(option.format->answer, "json") == 0) { + format = JSON; + } + else { + format = PLAIN; + } + if (sscanf(option.nsteps->answer, "%d", &nsteps) != 1 || nsteps < 1) G_fatal_error(_("%s = %s -- must be greater than zero"), option.nsteps->key, option.nsteps->answer); describe(option.map->answer, compact, no_data_str, range, windowed, nsteps, - as_int, flag.n->answer); + as_int, flag.n->answer, format); return EXIT_SUCCESS; } diff --git a/raster/r.describe/testsuite/test_r_describe.py b/raster/r.describe/testsuite/test_r_describe.py new file mode 100644 index 00000000000..34e647a4d12 --- /dev/null +++ b/raster/r.describe/testsuite/test_r_describe.py @@ -0,0 +1,455 @@ +import json + +from grass.gunittest.case import TestCase +from grass.gunittest.main import test +from grass.gunittest.gmodules import SimpleModule + + +class TestRDescribe(TestCase): + + @classmethod + def setUpClass(cls): + """Set up a temporary region and generate test data.""" + cls.use_temp_region() + cls.runModule("g.region", n=10, s=0, e=10, w=0, res=1) + + cls.runModule( + "r.mapcalc", + expression="int_map = if(row() % 2 == 0, col(), -col())", + overwrite=True, + ) + cls.runModule( + "r.mapcalc", + expression="float_map = if(row() == 5 && col() == 5, null(), if(row() % 2 == 0, col() * 0.5, -col() * 0.5))", + overwrite=True, + ) + + @classmethod + def tearDownClass(cls): + """Clean up after tests.""" + cls.runModule( + "g.remove", flags="f", type="raster", name=["int_map", "float_map"] + ) + cls.del_temp_region() + + def test_plain_describe_float(self): + """Test r.describe with the default output format and a float-type map.""" + module = SimpleModule("r.describe", map="float_map") + self.assertModule(module) + + result = module.outputs.stdout.strip().splitlines() + + expected_results = [ + "* -5.000000 thru -4.960784 -4.529412 thru -4.490196 -4.019608 thru -3.980392 ", + "-3.509804 thru -3.470588 -3.039216 thru -3.000000 -2.529412 thru -2.490196 ", + "-2.019608 thru -1.980392 -1.549020 thru -1.509804 -1.039216 thru -1.000000 ", + "-0.529412 thru -0.490196 0.450980 thru 0.490196 0.960784 thru 1.000000 ", + "1.470588 thru 1.509804 1.941176 thru 1.980392 2.450980 thru 2.490196 ", + "2.960784 thru 3.000000 3.431373 thru 3.470588 3.941176 thru 3.980392 ", + "4.450980 thru 4.490196 4.960784 thru 5.000000", + ] + + for i, component in enumerate(result): + self.assertEqual( + component, expected_results[i], f"Mismatch at line {i + 1}" + ) + + def test_plain_describe_with_one_flag_float(self): + """Test r.describe with the plain output format, the -1 flag, and a float-type map""" + module = SimpleModule("r.describe", map="float_map", flags="1") + self.assertModule(module) + + result = module.outputs.stdout.strip().splitlines() + + expected_results = [ + "*", + "-5.000000--4.960784", + "-4.529412--4.490196", + "-4.019608--3.980392", + "-3.509804--3.470588", + "-3.039216--3.000000", + "-2.529412--2.490196", + "-2.019608--1.980392", + "-1.549020--1.509804", + "-1.039216--1.000000", + "-0.529412--0.490196", + "0.450980-0.490196", + "0.960784-1.000000", + "1.470588-1.509804", + "1.941176-1.980392", + "2.450980-2.490196", + "2.960784-3.000000", + "3.431373-3.470588", + "3.941176-3.980392", + "4.450980-4.490196", + "4.960784-5.000000", + ] + + for i, component in enumerate(result): + self.assertEqual( + component, expected_results[i], f"Mismatch at line {i + 1}" + ) + + def test_plain_describe_with_r_flag_float(self): + """Test r.describe with the plain output format, the -r flag, and a float-type map""" + module = SimpleModule("r.describe", map="float_map", flags="r") + self.assertModule(module) + + result = module.outputs.stdout.strip().splitlines() + + expected_results = ["* -5.000000 thru 5.000000"] + + for i, component in enumerate(result): + self.assertEqual( + component, expected_results[i], f"Mismatch at line {i + 1}" + ) + + def test_plain_describe_with_i_flag_float(self): + """Test r.describe with the plain output format, the -i flag, and a float-type map""" + module = SimpleModule("r.describe", map="float_map", flags="i") + self.assertModule(module) + + result = module.outputs.stdout.strip().splitlines() + + expected_results = ["* -5 thru -1 1-5"] + + for i, component in enumerate(result): + self.assertEqual( + component, expected_results[i], f"Mismatch at line {i + 1}" + ) + + def test_plain_describe_with_n_flag_float(self): + """Test r.describe with the plain output format, the -n flag, and a float-type map""" + module = SimpleModule("r.describe", map="float_map", flags="n") + self.assertModule(module) + + result = module.outputs.stdout.strip().splitlines() + + expected_results = [ + "-5.000000 thru -4.960784 -4.529412 thru -4.490196 -4.019608 thru -3.980392 ", + "-3.509804 thru -3.470588 -3.039216 thru -3.000000 -2.529412 thru -2.490196 ", + "-2.019608 thru -1.980392 -1.549020 thru -1.509804 -1.039216 thru -1.000000 ", + "-0.529412 thru -0.490196 0.450980 thru 0.490196 0.960784 thru 1.000000 ", + "1.470588 thru 1.509804 1.941176 thru 1.980392 2.450980 thru 2.490196 ", + "2.960784 thru 3.000000 3.431373 thru 3.470588 3.941176 thru 3.980392 ", + "4.450980 thru 4.490196 4.960784 thru 5.000000", + ] + + for i, component in enumerate(result): + self.assertEqual( + component, expected_results[i], f"Mismatch at line {i + 1}" + ) + + def test_json_describe_float(self): + """Test r.describe with the json output format, and a float-type map""" + module = SimpleModule("r.describe", map="float_map", format="json") + self.assertModule(module) + + result = json.loads(module.outputs.stdout) + + expected_results = { + "has_nulls": True, + "ranges": [ + {"min": -5, "max": -4.96078431372549}, + {"min": -4.529411764705882, "max": -4.490196078431373}, + {"min": -4.019607843137255, "max": -3.980392156862745}, + {"min": -3.5098039215686274, "max": -3.4705882352941178}, + {"min": -3.0392156862745097, "max": -3}, + {"min": -2.5294117647058822, "max": -2.4901960784313726}, + {"min": -2.019607843137255, "max": -1.9803921568627452}, + {"min": -1.549019607843137, "max": -1.5098039215686274}, + {"min": -1.0392156862745097, "max": -1}, + {"min": -0.5294117647058822, "max": -0.4901960784313726}, + {"min": 0.4509803921568629, "max": 0.4901960784313726}, + {"min": 0.9607843137254903, "max": 1}, + {"min": 1.4705882352941178, "max": 1.5098039215686274}, + {"min": 1.9411764705882355, "max": 1.9803921568627452}, + {"min": 2.450980392156863, "max": 2.4901960784313726}, + {"min": 2.9607843137254903, "max": 3}, + {"min": 3.431372549019608, "max": 3.4705882352941178}, + {"min": 3.9411764705882355, "max": 3.980392156862745}, + {"min": 4.450980392156863, "max": 4.490196078431373}, + {"min": 4.96078431372549, "max": 5}, + ], + } + + self.assertDictEqual(expected_results, result) + + def test_json_describe_with_one_flag_float(self): + """Test r.describe with the json output format, the -1 flag, and a float-type map""" + module = SimpleModule("r.describe", map="float_map", flags="1", format="json") + self.assertModule(module) + + result = json.loads(module.outputs.stdout) + + expected_results = { + "has_nulls": True, + "ranges": [ + {"min": -5, "max": -4.96078431372549}, + {"min": -4.529411764705882, "max": -4.490196078431373}, + {"min": -4.019607843137255, "max": -3.980392156862745}, + {"min": -3.5098039215686274, "max": -3.4705882352941178}, + {"min": -3.0392156862745097, "max": -3}, + {"min": -2.5294117647058822, "max": -2.4901960784313726}, + {"min": -2.019607843137255, "max": -1.9803921568627452}, + {"min": -1.549019607843137, "max": -1.5098039215686274}, + {"min": -1.0392156862745097, "max": -1}, + {"min": -0.5294117647058822, "max": -0.4901960784313726}, + {"min": 0.4509803921568629, "max": 0.4901960784313726}, + {"min": 0.9607843137254903, "max": 1}, + {"min": 1.4705882352941178, "max": 1.5098039215686274}, + {"min": 1.9411764705882355, "max": 1.9803921568627452}, + {"min": 2.450980392156863, "max": 2.4901960784313726}, + {"min": 2.9607843137254903, "max": 3}, + {"min": 3.431372549019608, "max": 3.4705882352941178}, + {"min": 3.9411764705882355, "max": 3.980392156862745}, + {"min": 4.450980392156863, "max": 4.490196078431373}, + {"min": 4.96078431372549, "max": 5}, + ], + } + + self.assertDictEqual(expected_results, result) + + def test_json_describe_with_r_flag_float(self): + """Test r.describe with the json output format, the -r flag, and a float-type map""" + module = SimpleModule("r.describe", map="float_map", flags="r", format="json") + self.assertModule(module) + + result = json.loads(module.outputs.stdout) + + expected_results = {"has_nulls": True, "ranges": [{"min": -5, "max": 5}]} + + self.assertDictEqual(expected_results, result) + + def test_json_describe_with_i_flag_float(self): + """Test r.describe with the json output format and the -i flag.""" + module = SimpleModule("r.describe", map="float_map", flags="i", format="json") + self.assertModule(module) + + result = json.loads(module.outputs.stdout) + + expected_results = { + "has_nulls": True, + "ranges": [{"min": -5, "max": -1}, {"min": 1, "max": 5}], + } + + self.assertDictEqual(expected_results, result) + + def test_json_describe_with_n_flag_float(self): + """Test r.describe with the json output format, the -n flag, and a float-type map""" + module = SimpleModule("r.describe", map="float_map", flags="n", format="json") + self.assertModule(module) + + result = json.loads(module.outputs.stdout) + + expected_results = { + "ranges": [ + {"min": -5, "max": -4.96078431372549}, + {"min": -4.529411764705882, "max": -4.490196078431373}, + {"min": -4.019607843137255, "max": -3.980392156862745}, + {"min": -3.5098039215686274, "max": -3.4705882352941178}, + {"min": -3.0392156862745097, "max": -3}, + {"min": -2.5294117647058822, "max": -2.4901960784313726}, + {"min": -2.019607843137255, "max": -1.9803921568627452}, + {"min": -1.549019607843137, "max": -1.5098039215686274}, + {"min": -1.0392156862745097, "max": -1}, + {"min": -0.5294117647058822, "max": -0.4901960784313726}, + {"min": 0.4509803921568629, "max": 0.4901960784313726}, + {"min": 0.9607843137254903, "max": 1}, + {"min": 1.4705882352941178, "max": 1.5098039215686274}, + {"min": 1.9411764705882355, "max": 1.9803921568627452}, + {"min": 2.450980392156863, "max": 2.4901960784313726}, + {"min": 2.9607843137254903, "max": 3}, + {"min": 3.431372549019608, "max": 3.4705882352941178}, + {"min": 3.9411764705882355, "max": 3.980392156862745}, + {"min": 4.450980392156863, "max": 4.490196078431373}, + {"min": 4.96078431372549, "max": 5}, + ] + } + + self.assertDictEqual(expected_results, result) + + def test_plain_describe_int(self): + """Test r.describe with the default output format, and a int-type map""" + module = SimpleModule("r.describe", map="int_map") + self.assertModule(module) + + result = module.outputs.stdout.strip().splitlines() + + expected_results = [ + "-10 thru -1 1-10", + ] + + for i, component in enumerate(result): + self.assertEqual( + component, expected_results[i], f"Mismatch at line {i + 1}" + ) + + def test_plain_describe_with_one_flag_int(self): + """Test r.describe with the plain output format, the -1 flag, and a int-type map""" + module = SimpleModule("r.describe", map="int_map", flags="1") + self.assertModule(module) + + result = module.outputs.stdout.strip().splitlines() + + expected_results = [ + "-10", + "-9", + "-8", + "-7", + "-6", + "-5", + "-4", + "-3", + "-2", + "-1", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + ] + + for i, component in enumerate(result): + self.assertEqual( + component, expected_results[i], f"Mismatch at line {i + 1}" + ) + + def test_plain_describe_with_r_flag_int(self): + """Test r.describe with the plain output format, the -r flag, and a int-type map""" + module = SimpleModule("r.describe", map="int_map", flags="r") + self.assertModule(module) + + result = module.outputs.stdout.strip().splitlines() + + expected_results = ["-10 thru -1", "1 thru 10"] + + for i, component in enumerate(result): + self.assertEqual( + component, expected_results[i], f"Mismatch at line {i + 1}" + ) + + def test_plain_describe_with_i_flag_int(self): + """Test r.describe with the plain output format, the -i flag, and a int-type map""" + module = SimpleModule("r.describe", map="int_map", flags="i") + self.assertModule(module) + + result = module.outputs.stdout.strip().splitlines() + + expected_results = ["-10 thru -1 1-10"] + + for i, component in enumerate(result): + self.assertEqual( + component, expected_results[i], f"Mismatch at line {i + 1}" + ) + + def test_plain_describe_with_n_flag_int(self): + """Test r.describe with the plain output format, the -n flag, and a int-type map""" + module = SimpleModule("r.describe", map="int_map", flags="n") + self.assertModule(module) + + result = module.outputs.stdout.strip().splitlines() + + expected_results = ["-10 thru -1 1-10"] + + for i, component in enumerate(result): + self.assertEqual( + component, expected_results[i], f"Mismatch at line {i + 1}" + ) + + def test_json_describe_int(self): + """Test r.describe with the json output format, and a int-type map""" + module = SimpleModule("r.describe", map="int_map", format="json") + self.assertModule(module) + + result = json.loads(module.outputs.stdout) + + expected_results = { + "has_nulls": False, + "ranges": [{"min": -10, "max": -1}, {"min": 1, "max": 10}], + } + + self.assertDictEqual(expected_results, result) + + def test_json_describe_with_one_flag_int(self): + """Test r.describe with the json output format, the -1 flag, and a int-type map""" + module = SimpleModule("r.describe", map="int_map", flags="1", format="json") + self.assertModule(module) + + result = json.loads(module.outputs.stdout) + + expected_results = { + "has_nulls": False, + "values": [ + -10, + -9, + -8, + -7, + -6, + -5, + -4, + -3, + -2, + -1, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + ], + } + + self.assertDictEqual(expected_results, result) + + def test_json_describe_with_r_flag_int(self): + """Test r.describe with the json output format, the -r flag, and a int-type map""" + module = SimpleModule("r.describe", map="int_map", flags="r", format="json") + self.assertModule(module) + + result = json.loads(module.outputs.stdout) + + expected_results = { + "has_nulls": False, + "ranges": [{"min": -10, "max": -1}, {"min": 1, "max": 10}], + } + + self.assertDictEqual(expected_results, result) + + def test_json_describe_with_i_flag_int(self): + """Test r.describe with the json output format, the -i flag, and a int-type map""" + module = SimpleModule("r.describe", map="int_map", flags="i", format="json") + self.assertModule(module) + + result = json.loads(module.outputs.stdout) + + expected_results = { + "has_nulls": False, + "ranges": [{"min": -10, "max": -1}, {"min": 1, "max": 10}], + } + + self.assertDictEqual(expected_results, result) + + def test_json_describe_with_n_flag_int(self): + """Test r.describe with the json output format, the -n flag, and a int-type map""" + module = SimpleModule("r.describe", map="int_map", flags="n", format="json") + self.assertModule(module) + + result = json.loads(module.outputs.stdout) + + expected_results = {"ranges": [{"min": -10, "max": -1}, {"min": 1, "max": 10}]} + + self.assertDictEqual(expected_results, result) + + +if __name__ == "__main__": + test()