changeset 199:55031f07152b

wip, need test on SET_TAG_LABELS triggers by refcount
author Louis Opter <kalessin@kalessin.fr>
date Sun, 19 Jul 2015 17:39:41 -0700
parents 5609a782e502
children 623a0664aecb
files tag_untag_testing_wip.patch
diffstat 1 files changed, 608 insertions(+), 72 deletions(-) [+]
line wrap: on
line diff
--- a/tag_untag_testing_wip.patch	Sun Jul 19 16:14:17 2015 -0700
+++ b/tag_untag_testing_wip.patch	Sun Jul 19 17:39:41 2015 -0700
@@ -1,5 +1,5 @@
 # HG changeset patch
-# Parent  952ee0ab36a1500722322ef4720723279f9bee77
+# Parent  ce5c778a6562f80ec8f2476632920dc738b67903
 
 diff --git a/core/proto.c b/core/proto.c
 --- a/core/proto.c
@@ -13,7 +13,13 @@
      // the list of affected gateways so we can do SET_TAG_LABELS:
      SLIST_FOREACH(device, devices, link) {
          struct lgtd_lifx_gateway *gw = device->device->gw;
-@@ -283,6 +283,7 @@
+@@ -279,10 +279,12 @@
+     // SET_TAG_LABELS, this is idempotent, do it everytime so we can recover
+     // from any bad state:
+     LIST_FOREACH(site, &tag->sites, link) {
+-        int tag_id = lgtd_lifx_gateway_get_tag_id(site->gw, tag);
++        int tag_id = site->tag_id;
++        assert(tag_id > -1 && tag_id < LGTD_LIFX_GATEWAY_MAX_TAGS);
          struct lgtd_lifx_packet_tag_labels pkt = { .tags = 0 };
          pkt.tags = LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(tag_id);
          strncpy(pkt.label, tag_label, sizeof(pkt.label) - 1);
@@ -21,15 +27,24 @@
          bool enqueued = lgtd_lifx_gateway_send_to_site(
              site->gw, LGTD_LIFX_SET_TAG_LABELS, &pkt, sizeof(pkt)
          );
-@@ -302,6 +303,7 @@
+@@ -300,9 +302,13 @@
+         struct lgtd_lifx_bulb *bulb = device->device;
+         int tag_id = lgtd_lifx_gateway_get_tag_id(bulb->gw, tag);
          assert(tag_id > -1 && tag_id < LGTD_LIFX_GATEWAY_MAX_TAGS);
-         struct lgtd_lifx_packet_tags pkt;
-         pkt.tags = bulb->state.tags | LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(tag_id);
-+        lgtd_lifx_wire_encode_tags(&pkt);
-         lgtd_router_send_to_device(bulb, LGTD_LIFX_SET_TAGS, &pkt);
+-        struct lgtd_lifx_packet_tags pkt;
+-        pkt.tags = bulb->state.tags | LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(tag_id);
+-        lgtd_router_send_to_device(bulb, LGTD_LIFX_SET_TAGS, &pkt);
++        int tag_value = LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(tag_id);
++        if (!(bulb->state.tags & tag_value)) {
++            struct lgtd_lifx_packet_tags pkt;
++            pkt.tags = bulb->state.tags | tag_value;
++            lgtd_lifx_wire_encode_tags(&pkt);
++            lgtd_router_send_to_device(bulb, LGTD_LIFX_SET_TAGS, &pkt);
++        }
      }
  
-@@ -360,6 +362,7 @@
+     SEND_RESULT(client, true);
+@@ -360,6 +366,7 @@
              if (bulb->state.tags & tag_value) {
                  struct lgtd_lifx_packet_tags pkt;
                  pkt.tags = bulb->state.tags & ~tag_value;
@@ -289,7 +304,7 @@
  #include "tests_utils.h"
  
  #define MOCKED_ROUTER_TARGETS_TO_DEVICES
-@@ -8,33 +11,11 @@
+@@ -8,32 +11,12 @@
  #define MOCKED_ROUTER_DEVICE_LIST_FREE
  #include "tests_proto_utils.h"
  
@@ -297,7 +312,7 @@
 +
  static struct lgtd_router_device_list devices = 
      SLIST_HEAD_INITIALIZER(&devices);
- 
+-
 -static bool device_list_free_called = false;
 -
 -void
@@ -321,11 +336,12 @@
 -
 -    return &devices;
 -}
--
++static struct lgtd_router_device_list device_1_only =
++    SLIST_HEAD_INITIALIZER(&device_1_only);
+ 
  static bool send_to_device_called = false;
  
- void
-@@ -64,17 +45,121 @@
+@@ -64,17 +47,129 @@
          errx(1, "missing SET_TAGS payload");
      }
  
@@ -358,7 +374,7 @@
 +
 +    if (pkt_type != LGTD_LIFX_SET_TAG_LABELS) {
 +        errx(
-+            1, "got packet type %#x (expected %#hx)",
++            1, "got packet type %#x (expected %#x)",
 +            pkt_type, LGTD_LIFX_SET_TAG_LABELS
 +        );
 +    }
@@ -420,10 +436,487 @@
 +        );
 +    }
 +
++    tag_id = 0;
++
++    struct lgtd_lifx_tag *tag = lgtd_lifx_tagging_find_tag(tag_label);
++    if (!tag) {
++        errx(1, "tag %s wasn't found", tag_label);
++    }
++    lgtd_tests_add_tag_to_gw(tag, gw, tag_id);
++
 +    gateway_allocate_tag_id_called = true;
 +
++    return tag_id;
++}
++
++static bool device_list_free_called = false;
++
++void
++lgtd_router_device_list_free(struct lgtd_router_device_list *devices)
++{
++    if (!devices) {
++        lgtd_errx(1, "the device list must be passed");
++    }
++
++    device_list_free_called = true;
++}
++
++struct lgtd_router_device_list *
++lgtd_router_targets_to_devices(const struct lgtd_proto_target_list *targets)
++{
++    if (targets != FAKE_TARGET_LIST) {
++        lgtd_errx(1, "unexpected targets list");
++    }
++
++    return &device_1_only;
++}
++
+ static void
+ setup_devices(void)
+ {
+@@ -96,6 +191,7 @@
+     };
+     static struct lgtd_router_device device_1 = { .device = &bulb_1 };
+     SLIST_INSERT_HEAD(&devices, &device_1, link);
++    SLIST_INSERT_HEAD(&device_1_only, &device_1, link);
+ 
+     struct lgtd_lifx_tag *gw_2_tag_1 = lgtd_tests_insert_mock_tag("vapor");
+     struct lgtd_lifx_tag *gw_2_tag_2 = lgtd_tests_insert_mock_tag("d^-^b");
+@@ -127,15 +223,13 @@
+ int
+ main(void)
+ {
+-    struct lgtd_client client;
+-    memset(&client, 0, sizeof(client));
++    struct lgtd_client client = { .io = FAKE_BUFFEREVENT };
+ 
+     setup_devices();
+ 
+-    lgtd_proto_tag(&client, targets, "dub");
++    lgtd_proto_tag(&client, FAKE_TARGET_LIST, "dub");
+ 
+     const char expected[] = "true";
+-
+     if (client_write_buf_idx != sizeof(expected) - 1) {
+         lgtd_errx(
+             1,
+@@ -145,7 +239,6 @@
+             client_write_buf_idx, client_write_buf, expected
+         );
+     }
+-
+     if (memcmp(expected, client_write_buf, sizeof(expected) - 1)) {
+         lgtd_errx(
+             1, "got %.*s instead of %s",
+@@ -153,11 +246,14 @@
+         );
+     }
+ 
++    if (!gateway_send_to_site_called) {
++        lgtd_errx(1, "SET_TAG_LABELS wasn't sent");
++    }
+     if (!device_list_free_called) {
+         lgtd_errx(1, "the list of devices hasn't been freed");
+     }
+     if (!send_to_device_called) {
+-        lgtd_errx(1, "nothing was send to any device");
++        lgtd_errx(1, "SET_TAGS wasn't send to any device");
+     }
+ 
+     return 0;
+diff --git a/tests/core/proto/test_proto_tag_create_lifx_gw_tag_ids_full.c b/tests/core/proto/test_proto_tag_create_lifx_gw_tag_ids_full.c
+--- a/tests/core/proto/test_proto_tag_create_lifx_gw_tag_ids_full.c
++++ b/tests/core/proto/test_proto_tag_create_lifx_gw_tag_ids_full.c
+@@ -0,0 +1,210 @@
++#include "proto.c"
++
++#include "mock_client_buf.h"
++#define MOCKED_LIFX_GATEWAY_SEND_TO_SITE
++#define MOCKED_LIFX_GATEWAY_ALLOCATE_TAG_ID
++#include "tests_shims.h"
++#include "tests_utils.h"
++
++#define MOCKED_CLIENT_SEND_ERROR
++#define MOCKED_ROUTER_TARGETS_TO_DEVICES
++#define MOCKED_ROUTER_SEND_TO_DEVICE
++#define MOCKED_ROUTER_DEVICE_LIST_FREE
++#include "tests_proto_utils.h"
++
++#define FAKE_TARGET_LIST (void *)0x2a
++
++static struct lgtd_router_device_list devices =
++    SLIST_HEAD_INITIALIZER(&devices);
++static struct lgtd_router_device_list device_1_only =
++    SLIST_HEAD_INITIALIZER(&device_1_only);
++
++static bool client_send_error_called = false;
++
++void
++lgtd_client_send_error(struct lgtd_client *client,
++                       enum lgtd_client_error_code error,
++                       const char *msg)
++{
++    if (!client) {
++        errx(1, "client_send_error called without a client");
++    }
++
++    if (!error) {
++        errx(1, "client_send_error called without an error code");
++    }
++
++    if (!msg) {
++        errx(1, "client_send_error called without an error message");
++    }
++
++    client_send_error_called = true;
++}
++
++static bool send_to_device_called = false;
++
++void
++lgtd_router_send_to_device(struct lgtd_lifx_bulb *bulb,
++                           enum lgtd_lifx_packet_type pkt_type,
++                           void *pkt)
++{
++    (void)bulb;
++    (void)pkt_type;
++    (void)pkt;
++
++    send_to_device_called = true;
++}
++
++static bool gateway_send_to_site_called = false;
++
++bool
++lgtd_lifx_gateway_send_to_site(struct lgtd_lifx_gateway *gw,
++                               enum lgtd_lifx_packet_type pkt_type,
++                               const void *pkt,
++                               int pkt_size)
++{
++    (void)gw;
++    (void)pkt_type;
++    (void)pkt;
++    (void)pkt_size;
++
++    gateway_send_to_site_called = true;
++
++    return true;
++}
++
++static bool gateway_allocate_tag_id_called = false;
++
++int
++lgtd_lifx_gateway_allocate_tag_id(struct lgtd_lifx_gateway *gw,
++                                  int tag_id,
++                                  const char *tag_label)
++{
++    if (gateway_allocate_tag_id_called) {
++        errx(
++            1, "lgtd_lifx_gateway_allocate_tag_id "
++            "should have been called once only"
++        );
++    }
++
++    if (tag_id != -1) {
++        errx(
++            1, "lgtd_lifx_gateway_allocate_tag_id "
++            "tag_id %d (expected -1)", tag_id
++        );
++    }
++
++    if (!gw) {
++        errx(
++            1, "lgtd_lifx_gateway_allocate_tag_id "
++            "must be called with gateway"
++        );
++    }
++
++    if (!tag_label) {
++        errx(
++            1, "lgtd_lifx_gateway_allocate_tag_id "
++            "must be called with a tag_label"
++        );
++    }
++
++    return -1;  // no more tag id available
++}
++
++static bool device_list_free_called = false;
++
++void
++lgtd_router_device_list_free(struct lgtd_router_device_list *devices)
++{
++    if (!devices) {
++        lgtd_errx(1, "the device list must be passed");
++    }
++
++    device_list_free_called = true;
++}
++
++struct lgtd_router_device_list *
++lgtd_router_targets_to_devices(const struct lgtd_proto_target_list *targets)
++{
++    if (targets != FAKE_TARGET_LIST) {
++        lgtd_errx(1, "unexpected targets list");
++    }
++
++    return &device_1_only;
++}
++
++static void
++setup_devices(void)
++{
++    static struct lgtd_lifx_gateway gw_bulb_1 = {
++        .bulbs = LIST_HEAD_INITIALIZER(&gw_bulb_1.bulbs)
++    };
++    static struct lgtd_lifx_bulb bulb_1 = {
++        .addr = { 1, 2, 3, 4, 5 },
++        .state = {
++            .hue = 0xaaaa,
++            .saturation = 0xffff,
++            .brightness = 0xbbbb,
++            .kelvin = 3600,
++            .label = "wave",
++            .power = LGTD_LIFX_POWER_ON,
++            .tags = 0
++        },
++        .gw = &gw_bulb_1
++    };
++    static struct lgtd_router_device device_1 = { .device = &bulb_1 };
++    SLIST_INSERT_HEAD(&devices, &device_1, link);
++    SLIST_INSERT_HEAD(&device_1_only, &device_1, link);
++
++    struct lgtd_lifx_tag *gw_2_tag_1 = lgtd_tests_insert_mock_tag("vapor");
++    struct lgtd_lifx_tag *gw_2_tag_2 = lgtd_tests_insert_mock_tag("d^-^b");
++    struct lgtd_lifx_tag *gw_2_tag_3 = lgtd_tests_insert_mock_tag("wave~");
++    static struct lgtd_lifx_gateway gw_bulb_2 = {
++        .bulbs = LIST_HEAD_INITIALIZER(&gw_bulb_2.bulbs),
++        .tag_ids = 0x7
++    };
++    lgtd_tests_add_tag_to_gw(gw_2_tag_1, &gw_bulb_2, 0);
++    lgtd_tests_add_tag_to_gw(gw_2_tag_2, &gw_bulb_2, 1);
++    lgtd_tests_add_tag_to_gw(gw_2_tag_3, &gw_bulb_2, 2);
++    static struct lgtd_lifx_bulb bulb_2 = {
++        .addr = { 5, 4, 3, 2, 1 },
++        .state = {
++            .hue = 0x0000,
++            .saturation = 0x0000,
++            .brightness = 0xffff,
++            .kelvin = 4000,
++            .label = "",
++            .power = LGTD_LIFX_POWER_OFF,
++            .tags = 0x3
++        },
++        .gw = &gw_bulb_2
++    };
++    static struct lgtd_router_device device_2 = { .device = &bulb_2 };
++    SLIST_INSERT_HEAD(&devices, &device_2, link);
++}
++
++int
++main(void)
++{
++    struct lgtd_client client = { .io = FAKE_BUFFEREVENT };
++
++    setup_devices();
++
++    lgtd_proto_tag(&client, FAKE_TARGET_LIST, "dub");
++
++
++    if (gateway_send_to_site_called) {
++        lgtd_errx(1, "SET_TAG_LABELS shouldn't have been sent");
++    }
++    if (!device_list_free_called) {
++        lgtd_errx(1, "the list of devices hasn't been freed");
++    }
++    if (send_to_device_called) {
++        lgtd_errx(1, "SET_TAGS shouldn't have been to any device");
++    }
++    if (!client_send_error_called) {
++        lgtd_errx(1, "client_send_error should have been called");
++    }
++
 +    return 0;
 +}
+diff --git a/tests/core/proto/test_proto_tag_update.c b/tests/core/proto/test_proto_tag_update.c
+--- a/tests/core/proto/test_proto_tag_update.c
++++ b/tests/core/proto/test_proto_tag_update.c
+@@ -0,0 +1,290 @@
++#include "proto.c"
++
++#include "mock_client_buf.h"
++#define MOCKED_LIFX_GATEWAY_SEND_TO_SITE
++#define MOCKED_LIFX_GATEWAY_ALLOCATE_TAG_ID
++#include "tests_shims.h"
++#include "tests_utils.h"
++
++#define MOCKED_ROUTER_TARGETS_TO_DEVICES
++#define MOCKED_ROUTER_SEND_TO_DEVICE
++#define MOCKED_ROUTER_DEVICE_LIST_FREE
++#include "tests_proto_utils.h"
++
++#define FAKE_TARGET_LIST (void *)0x2a
++
++static struct lgtd_router_device_list devices =
++    SLIST_HEAD_INITIALIZER(&devices);
++
++static bool send_to_device_called = false;
++
++void
++lgtd_router_send_to_device(struct lgtd_lifx_bulb *bulb,
++                           enum lgtd_lifx_packet_type pkt_type,
++                           void *pkt)
++{
++    if (send_to_device_called) {
++        errx(1, "lgtd_router_send_to_device should have been called once only");
++    }
++
++    if (!bulb) {
++        errx(1, "lgtd_router_send_to_device must be called with a bulb");
++    }
++
++    uint8_t expected_addr[LGTD_LIFX_ADDR_LENGTH] = { 5, 4, 3, 2, 1 };
++    if (memcmp(bulb->addr, expected_addr, LGTD_LIFX_ADDR_LENGTH)) {
++        errx(
++            1, "got bulb with addr %s (expected %s)",
++            lgtd_addrtoa(bulb->addr), lgtd_addrtoa(expected_addr)
++        );
++    }
++
++    if (pkt_type != LGTD_LIFX_SET_TAGS) {
++        errx(
++            1, "got packet type %d (expected %d)", pkt_type, LGTD_LIFX_SET_TAGS
++        );
++    }
++
++    if (!pkt) {
++        errx(1, "missing SET_TAGS payload");
++    }
++
++    const struct lgtd_lifx_packet_tags *pkt_tags = pkt;
++    uint64_t tags = le64toh(pkt_tags->tags);
++
++    if (tags != 0x7) {
++        errx(
++            1, "invalid SET_TAGS payload=%#jx (expected %#x)",
++            (uintmax_t)tags, 0x7
++        );
++    }
++
++    send_to_device_called = true;
++}
++
++static bool gateway_send_to_site_called_for_gw_1 = false;
++static bool gateway_send_to_site_called_for_gw_2 = false;
++
++bool
++lgtd_lifx_gateway_send_to_site(struct lgtd_lifx_gateway *gw,
++                               enum lgtd_lifx_packet_type pkt_type,
++                               const void *pkt,
++                               int pkt_size)
++{
++    if (!gw) {
++        errx(1, "missing gateway");
++    }
++
++    if (pkt_type != LGTD_LIFX_SET_TAG_LABELS) {
++        errx(
++            1, "got packet type %#x (expected %#x)",
++            pkt_type, LGTD_LIFX_SET_TAG_LABELS
++        );
++    }
++
++    const struct lgtd_lifx_packet_tag_labels *pkt_tag_labels = pkt;
++    uint64_t tags = le64toh(pkt_tag_labels->tags);
++
++    if (strcmp(pkt_tag_labels->label, "dub")) {
++        errx(1, "got label %s (expected dub)", pkt_tag_labels->label);
++    }
++
++    if (pkt_size != sizeof(*pkt_tag_labels)) {
++        errx(
++            1, "got pkt_size %d (expected %d)",
++            pkt_size, (int)sizeof(*pkt_tag_labels)
++        );
++    }
++
++    if (gw->site.as_integer == 42) {
++        if (tags != 0x1) {
++            errx(1, "got tags %#jx (expected %#x)", (uintmax_t)tags, 0x1);
++        }
++        if (gateway_send_to_site_called_for_gw_1) {
++            errx(1, "LGTD_LIFX_SET_TAG_LABELS already called for gw 1");
++        }
++        gateway_send_to_site_called_for_gw_1 = true;
++    } else if (gw->site.as_integer == 44) {
++        if (tags != 0x4) {
++            errx(1, "got tags %#jx (expected %#x)", (uintmax_t)tags, 0x4);
++        }
++        if (gateway_send_to_site_called_for_gw_2) {
++            errx(1, "LGTD_LIFX_SET_TAG_LABELS already called for gw 2");
++        }
++        gateway_send_to_site_called_for_gw_2 = true;
++    } else {
++        errx(1, "LGTD_LIFX_SET_TAG_LABELS received an invalid gateway");
++    }
++
++    return true;
++}
++
++static bool gateway_allocate_tag_id_called = false;
++
++int
++lgtd_lifx_gateway_allocate_tag_id(struct lgtd_lifx_gateway *gw,
++                                  int tag_id,
++                                  const char *tag_label)
++{
++    if (gateway_allocate_tag_id_called) {
++        errx(
++            1, "lgtd_lifx_gateway_allocate_tag_id "
++            "should have been called once only"
++        );
++    }
++
++    if (tag_id != -1) {
++        errx(
++            1, "lgtd_lifx_gateway_allocate_tag_id "
++            "tag_id %d (expected -1)", tag_id
++        );
++    }
++
++    if (!gw) {
++        errx(
++            1, "lgtd_lifx_gateway_allocate_tag_id "
++            "must be called with gateway"
++        );
++    }
++
++    if (!tag_label) {
++        errx(
++            1, "lgtd_lifx_gateway_allocate_tag_id "
++            "must be called with a tag_label"
++        );
++    }
++
++    if (gw->site.as_integer != 44) {
++        errx(
++            1, "lgtd_lifx_gateway_allocate_tag_id got the wrong gateway "
++            "%#jx (expected %d)", (uintmax_t)gw->site.as_integer, 44
++        );
++    }
++
++    tag_id = 2;
++
++    struct lgtd_lifx_tag *tag = lgtd_lifx_tagging_find_tag(tag_label);
++    if (!tag) {
++        errx(1, "tag %s wasn't found", tag_label);
++    }
++    lgtd_tests_add_tag_to_gw(tag, gw, tag_id);
++
++    gateway_allocate_tag_id_called = true;
++
++    return tag_id;
++}
 +
 +static bool device_list_free_called = false;
 +
@@ -447,68 +940,96 @@
 +    return &devices;
 +}
 +
- static void
- setup_devices(void)
- {
-@@ -127,15 +212,13 @@
- int
- main(void)
- {
--    struct lgtd_client client;
--    memset(&client, 0, sizeof(client));
-+    struct lgtd_client client = { .io = FAKE_BUFFEREVENT };
- 
-     setup_devices();
- 
--    lgtd_proto_tag(&client, targets, "dub");
-+    lgtd_proto_tag(&client, FAKE_TARGET_LIST, "dub");
- 
-     const char expected[] = "true";
--
-     if (client_write_buf_idx != sizeof(expected) - 1) {
-         lgtd_errx(
-             1,
-@@ -145,7 +228,6 @@
-             client_write_buf_idx, client_write_buf, expected
-         );
-     }
--
-     if (memcmp(expected, client_write_buf, sizeof(expected) - 1)) {
-         lgtd_errx(
-             1, "got %.*s instead of %s",
-@@ -153,11 +235,14 @@
-         );
-     }
- 
-+    if (!gateway_send_to_site_called) {
-+        lgtd_errx(1, "SET_TAG_LABELS wasn't sent");
-+    }
-     if (!device_list_free_called) {
-         lgtd_errx(1, "the list of devices hasn't been freed");
-     }
-     if (!send_to_device_called) {
--        lgtd_errx(1, "nothing was send to any device");
-+        lgtd_errx(1, "SET_TAGS wasn't send to any device");
-     }
- 
-     return 0;
-diff --git a/tests/core/proto/test_proto_tag_create_lifx_gw_tag_ids_full.c b/tests/core/proto/test_proto_tag_create_lifx_gw_tag_ids_full.c
---- a/tests/core/proto/test_proto_tag_create_lifx_gw_tag_ids_full.c
-+++ b/tests/core/proto/test_proto_tag_create_lifx_gw_tag_ids_full.c
-@@ -0,0 +1,6 @@
++static void
++setup_devices(void)
++{
++    static struct lgtd_lifx_gateway gw_bulb_1 = {
++        .bulbs = LIST_HEAD_INITIALIZER(&gw_bulb_1.bulbs),
++        .site = { .as_integer = 42 }
++    };
++    static struct lgtd_lifx_bulb bulb_1 = {
++        .addr = { 1, 2, 3, 4, 5 },
++        .state = {
++            .hue = 0xaaaa,
++            .saturation = 0xffff,
++            .brightness = 0xbbbb,
++            .kelvin = 3600,
++            .label = "wave",
++            .power = LGTD_LIFX_POWER_ON,
++            .tags = 1
++        },
++        .gw = &gw_bulb_1
++    };
++    static struct lgtd_router_device device_1 = { .device = &bulb_1 };
++    SLIST_INSERT_HEAD(&devices, &device_1, link);
++    struct lgtd_lifx_tag *gw_1_tag_1 = lgtd_tests_insert_mock_tag("dub");
++    lgtd_tests_add_tag_to_gw(gw_1_tag_1, &gw_bulb_1, 0);
++
++    struct lgtd_lifx_tag *gw_2_tag_1 = lgtd_tests_insert_mock_tag("vapor");
++    struct lgtd_lifx_tag *gw_2_tag_2 = lgtd_tests_insert_mock_tag("d^-^b");
++    static struct lgtd_lifx_gateway gw_bulb_2 = {
++        .bulbs = LIST_HEAD_INITIALIZER(&gw_bulb_2.bulbs),
++        .site = { .as_integer = 44 },
++        .tag_ids = 0x3
++    };
++    lgtd_tests_add_tag_to_gw(gw_2_tag_1, &gw_bulb_2, 0);
++    lgtd_tests_add_tag_to_gw(gw_2_tag_2, &gw_bulb_2, 1);
++    static struct lgtd_lifx_bulb bulb_2 = {
++        .addr = { 5, 4, 3, 2, 1 },
++        .state = {
++            .hue = 0x0000,
++            .saturation = 0x0000,
++            .brightness = 0xffff,
++            .kelvin = 4000,
++            .label = "",
++            .power = LGTD_LIFX_POWER_OFF,
++            .tags = 0x3
++        },
++        .gw = &gw_bulb_2
++    };
++    static struct lgtd_router_device device_2 = { .device = &bulb_2 };
++    SLIST_INSERT_HEAD(&devices, &device_2, link);
++}
++
 +int
 +main(void)
 +{
++    struct lgtd_client client = { .io = FAKE_BUFFEREVENT };
 +
-+    return 0;
-+}
-diff --git a/tests/core/proto/test_proto_tag_update.c b/tests/core/proto/test_proto_tag_update.c
---- a/tests/core/proto/test_proto_tag_update.c
-+++ b/tests/core/proto/test_proto_tag_update.c
-@@ -0,0 +1,5 @@
-+int
-+main(void)
-+{
++    setup_devices();
++
++    lgtd_proto_tag(&client, FAKE_TARGET_LIST, "dub");
++
++    const char expected[] = "true";
++    if (client_write_buf_idx != sizeof(expected) - 1) {
++        lgtd_errx(
++            1,
++            "%d bytes written, expected %lu "
++            "(got %.*s instead of %s)",
++            client_write_buf_idx, sizeof(expected) - 1UL,
++            client_write_buf_idx, client_write_buf, expected
++        );
++    }
++    if (memcmp(expected, client_write_buf, sizeof(expected) - 1)) {
++        lgtd_errx(
++            1, "got %.*s instead of %s",
++            client_write_buf_idx, client_write_buf, expected
++        );
++    }
++
++    if (!gateway_send_to_site_called_for_gw_1) {
++        lgtd_errx(1, "SET_TAG_LABELS wasn't sent to gw 1");
++    }
++    if (!gateway_send_to_site_called_for_gw_2) {
++        lgtd_errx(1, "SET_TAG_LABELS wasn't sent to gw 2");
++    }
++    if (!device_list_free_called) {
++        lgtd_errx(1, "the list of devices hasn't been freed");
++    }
++    if (!send_to_device_called) {
++        lgtd_errx(1, "SET_TAGS wasn't send to any device");
++    }
++
 +    return 0;
 +}
 diff --git a/tests/core/proto/test_proto_untag.c b/tests/core/proto/test_proto_untag.c
@@ -638,3 +1159,18 @@
 +    return -1;
 +}
 +#endif
+diff --git a/tests/core/tests_utils.c b/tests/core/tests_utils.c
+--- a/tests/core/tests_utils.c
++++ b/tests/core/tests_utils.c
+@@ -105,7 +105,10 @@
+     struct lgtd_lifx_site *site = calloc(1, sizeof(*site));
+     site->gw = gw;
+     site->tag_id = tag_id;
++    LIST_INSERT_HEAD(&tag->sites, site, link);
++
+     gw->tags[tag_id] = tag;
+-    LIST_INSERT_HEAD(&tag->sites, site, link);
++    gw->tag_ids |= LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(tag_id);
++
+     return site;
+ }