Add test case for stack usage (#484)
diff --git a/tests/stackusage/SConscript b/tests/stackusage/SConscript
new file mode 100644
index 0000000..a0c6669
--- /dev/null
+++ b/tests/stackusage/SConscript
@@ -0,0 +1,7 @@
+# Test stack usage
+
+Import("env")
+
+env.NanopbProto(["stackusage", "stackusage.options"])
+test = env.Program(["stackusage.c", "stackusage.pb.c", "$COMMON/pb_encode.o", "$COMMON/pb_decode.o", "$COMMON/pb_common.o"])
+env.RunTest(test)
diff --git a/tests/stackusage/stackusage.c b/tests/stackusage/stackusage.c
new file mode 100644
index 0000000..6e4de06
--- /dev/null
+++ b/tests/stackusage/stackusage.c
@@ -0,0 +1,94 @@
+#include <pb_encode.h>
+#include <pb_decode.h>
+#include <string.h>
+#include <assert.h>
+#include <stdio.h>
+#include "stackusage.pb.h"
+
+static uint8_t g_msgbuf[256];
+static size_t g_msglen;
+
+/* This is a hacky way to measure actual stack usage of functions.
+ * It works by copying the stack to a global variable, and then
+ * finding the lowest location that has been modified.
+ * Currently this assumes that the platform uses a descending stack.
+ */
+#define MAX_STACK_ENTRIES 1024
+static uint32_t g_stackbuf[MAX_STACK_ENTRIES];
+static uint32_t *g_stackptr;
+
+void start_stack_measuring()
+{
+    uint32_t i = 0;
+    g_stackptr = &i - MAX_STACK_ENTRIES;
+    for (i = 0; i < MAX_STACK_ENTRIES; i++)
+    {
+        g_stackbuf[i] = g_stackptr[i];
+    }
+}
+
+int end_stack_measuring()
+{
+    uint32_t i = 0;
+    for (i = 0; i < MAX_STACK_ENTRIES; i++)
+    {
+        if (g_stackbuf[i] != g_stackptr[i])
+        {
+            return (MAX_STACK_ENTRIES - i) * sizeof(uint32_t);
+        }
+    }
+    assert(false);
+    return 0;
+}
+
+void do_encode()
+{
+    pb_ostream_t stream = pb_ostream_from_buffer(g_msgbuf, sizeof(g_msgbuf));
+    SettingsGroup msg = SettingsGroup_init_zero;
+    bool status;
+
+    msg.has_settings = true;
+    msg.settings.id = 1;
+    strcpy(msg.settings.name, "abcd");
+    msg.settings.en = true;
+    msg.settings.has_begin = true;
+    msg.settings.begin.label = 1234;
+    msg.settings.begin.properties_count = 1;
+    msg.settings.begin.properties[0].which_field = Property_DeviceA_Mode_tag;
+    msg.settings.begin.properties[0].field.DeviceA_Mode = 2;
+
+    status = pb_encode(&stream, SettingsGroup_fields, &msg);
+    g_msglen = stream.bytes_written;
+    assert(status);
+    assert(g_msglen > 10);
+}
+
+void do_decode()
+{
+    pb_istream_t stream = pb_istream_from_buffer(g_msgbuf, g_msglen);
+    SettingsGroup msg = SettingsGroup_init_zero;
+    bool status;
+
+    status = pb_decode(&stream, SettingsGroup_fields, &msg);
+    assert(status);
+    assert(msg.settings.begin.properties[0].field.DeviceA_Mode == 2);
+}
+
+int main()
+{
+    int stack_encode, stack_decode;
+
+    start_stack_measuring();
+    do_encode();
+    stack_encode = end_stack_measuring();
+
+    start_stack_measuring();
+    do_decode();
+    stack_decode = end_stack_measuring();
+
+    /* Print machine-readable to stdout and user-readable to stderr */
+    printf("%d %d\n", stack_encode, stack_decode);
+    fprintf(stderr, "Stack usage: encode %d bytes, decode %d bytes\n",
+            stack_encode, stack_decode);
+    return 0;
+}
diff --git a/tests/stackusage/stackusage.options b/tests/stackusage/stackusage.options
new file mode 100644
index 0000000..a0d5841
--- /dev/null
+++ b/tests/stackusage/stackusage.options
@@ -0,0 +1,3 @@
+* long_names     : false
+SettingsGroup.Settings.name               max_length : 32
+SettingsGroup.Settings.Command.properties max_count  : 6
diff --git a/tests/stackusage/stackusage.proto b/tests/stackusage/stackusage.proto
new file mode 100644
index 0000000..0f0437f
--- /dev/null
+++ b/tests/stackusage/stackusage.proto
@@ -0,0 +1,45 @@
+syntax = "proto3";
+
+message Property
+{
+    enum Mode
+    {
+        INVALID = 0;
+        A = 1;
+        B = 2;
+        C = 3;
+    }
+
+    oneof field
+    {
+        Mode   DeviceA_Mode     = 1;
+        uint32 DeviceA_Size     = 2;
+        uint32 DeviceA_Length   = 3;
+
+        Mode   DeviceB_Mode     = 4;
+        uint32 DeviceB_Size     = 5;
+        uint32 DeviceB_Length   = 6;
+    }
+}
+
+message SettingsGroup
+{
+    message Settings
+    {
+        message Command
+        {
+            uint32 label                 = 1;
+            repeated Property properties = 2;
+        }
+
+        uint32      id      = 1;
+        string      name    = 2;
+        uint32      flags   = 3;
+        bool        en      = 5;
+        Command     begin   = 6;
+        Command     end     = 7;
+    }
+
+    Settings settings   = 1;
+    uint32 version      = 2;
+}