diff mbox series

[GSoC] t/: migrate helper/test-example-decorate to the unit testing framework

Message ID 20240528125837.31090-1-shyamthakkar001@gmail.com (mailing list archive)
State New, archived
Headers show
Series [GSoC] t/: migrate helper/test-example-decorate to the unit testing framework | expand

Commit Message

Ghanshyam Thakkar May 28, 2024, 12:58 p.m. UTC
helper/test-example-decorate.c along with t9004-example.sh provide
an example of how to use the functions in decorate.h (which provides
a data structure that associates Git objects to void pointers) and
also test their output.

Migrate them to the new unit testing framework for better debugging
and runtime performance.

Mentored-by: Christian Couder <chriscool@tuxfamily.org>
Mentored-by: Kaartic Sivaraam <kaartic.sivaraam@gmail.com>
Signed-off-by: Ghanshyam Thakkar <shyamthakkar001@gmail.com>
---
 Makefile                          |  2 +-
 decorate.h                        |  2 +-
 t/helper/test-example-decorate.c  | 78 ------------------------------
 t/helper/test-tool.c              |  1 -
 t/helper/test-tool.h              |  1 -
 t/t9004-example.sh                | 12 -----
 t/unit-tests/t-example-decorate.c | 80 +++++++++++++++++++++++++++++++
 7 files changed, 82 insertions(+), 94 deletions(-)
 delete mode 100644 t/helper/test-example-decorate.c
 delete mode 100755 t/t9004-example.sh
 create mode 100644 t/unit-tests/t-example-decorate.c

Comments

Junio C Hamano May 29, 2024, 9:41 p.m. UTC | #1
Ghanshyam Thakkar <shyamthakkar001@gmail.com> writes:

> +struct test_vars {
> +	struct object *one, *two, *three;
> +	struct decoration n;
> +	int decoration_a, decoration_b;
> +};
> +
> +static void t_add(struct test_vars *vars)
> +{
> +	void *ret = add_decoration(&vars->n, vars->one, &vars->decoration_a);
> +
> +	if (!check(ret == NULL))
> +		test_msg("when adding a brand-new object, NULL should be returned");
> +	ret = add_decoration(&vars->n, vars->two, NULL);
> +	if (!check(ret == NULL))
> +		test_msg("when adding a brand-new object, NULL should be returned");
> +}
> +
> +static void t_readd(struct test_vars *vars)
> +{
> +	void *ret = add_decoration(&vars->n, vars->one, NULL);
> +
> +	if (!check(ret == &vars->decoration_a))
> +		test_msg("when readding an already existing object, existing decoration should be returned");
> +	ret = add_decoration(&vars->n, vars->two, &vars->decoration_b);
> +	if (!check(ret == NULL))
> +		test_msg("when readding an already existing object, existing decoration should be returned");
> +}
> +
> +static void t_lookup(struct test_vars *vars)
> +{
> +	void *ret = lookup_decoration(&vars->n, vars->one);
> +
> +	if (!check(ret == NULL))
> +		test_msg("lookup should return added declaration");
> +	ret = lookup_decoration(&vars->n, vars->two);
> +	if (!check(ret == &vars->decoration_b))
> +		test_msg("lookup should return added declaration");
> +	ret = lookup_decoration(&vars->n, vars->three);
> +	if (!check(ret == NULL))
> +		test_msg("lookup for unknown object should return NULL");
> +}
> +
> +static void t_loop(struct test_vars *vars)
> +{
> +	int i, objects_noticed = 0;
> +
> +	for (i = 0; i < vars->n.size; i++) {
> +		if (vars->n.entries[i].base)
> +			objects_noticed++;
> +	}
> +	if (!check_int(objects_noticed, ==, 2))
> +		test_msg("should have 2 objects");
> +}
> +
> +int cmd_main(int argc UNUSED, const char **argv UNUSED)
> +{
> +	struct object_id one_oid = { { 1 } }, two_oid = { { 2 } }, three_oid = { { 3 } };
> +	struct test_vars vars = { 0 };
> +
> +	vars.one = lookup_unknown_object(the_repository, &one_oid);
> +	vars.two = lookup_unknown_object(the_repository, &two_oid);
> +	vars.three = lookup_unknown_object(the_repository, &three_oid);
> +
> +	TEST(t_add(&vars),
> +	     "Add 2 objects, one with a non-NULL decoration and one with a NULL decoration.");
> +	TEST(t_readd(&vars),
> +	     "When re-adding an already existing object, the old decoration is returned.");
> +	TEST(t_lookup(&vars),
> +	     "Lookup returns the added declarations, or NULL if the object was never added.");
> +	TEST(t_loop(&vars), "The user can also loop through all entries.");

These tests as a whole look like a faithful copy of the original
done by cmd__example_decorate().

I do not understand the criteria used to split them into the four
separate helper functions.  It is not like they can be reused or
reordered---for example, t_readd() must be done after t_add() has
been done.

What benefit are you trying to get out of these split?  IOW, what
are we gaining by having four separate helper functions, instead of
testing all of these same things in a single helper function t_all
with something like

	TEST(t_all(&vars), "Do all decorate tests.");

in cmd_main()?  If there is a concrete benefit of having larger
number of smaller tests, would it make the result even better if we
split t_add() further into t_add_one() that adds one with deco_a and
t_add_two() that adds two with NULL?  The other helpers can of
course be further split into individual pieces the same way.  What
ere the criteria used to decide where to stop and use these four?

Thanks.
Christian Couder May 30, 2024, 6:55 a.m. UTC | #2
On Wed, May 29, 2024 at 11:41 PM Junio C Hamano <gitster@pobox.com> wrote:
> Ghanshyam Thakkar <shyamthakkar001@gmail.com> writes:

> > +     TEST(t_add(&vars),
> > +          "Add 2 objects, one with a non-NULL decoration and one with a NULL decoration.");
> > +     TEST(t_readd(&vars),
> > +          "When re-adding an already existing object, the old decoration is returned.");
> > +     TEST(t_lookup(&vars),
> > +          "Lookup returns the added declarations, or NULL if the object was never added.");
> > +     TEST(t_loop(&vars), "The user can also loop through all entries.");
>
> These tests as a whole look like a faithful copy of the original
> done by cmd__example_decorate().
>
> I do not understand the criteria used to split them into the four
> separate helper functions.  It is not like they can be reused or
> reordered---for example, t_readd() must be done after t_add() has
> been done.
>
> What benefit are you trying to get out of these split?  IOW, what
> are we gaining by having four separate helper functions, instead of
> testing all of these same things in a single helper function t_all
> with something like
>
>         TEST(t_all(&vars), "Do all decorate tests.");
>
> in cmd_main()?  If there is a concrete benefit of having larger
> number of smaller tests, would it make the result even better if we
> split t_add() further into t_add_one() that adds one with deco_a and
> t_add_two() that adds two with NULL?  The other helpers can of
> course be further split into individual pieces the same way.  What
> ere the criteria used to decide where to stop and use these four?

The original code has some kind of "sections" (or paragraphs)
separated using comments like:

      /*
       * Add 2 objects, one with a non-NULL decoration and one with a NULL
       * decoration.
       */

or:

      /*
       * When re-adding an already existing object, the old decoration is
       * returned.
       */

I think it makes sense to separate the code using functions matching
these "sections" and to reuse each comment in the TEST() macro that
calls the corresponding function. If this patch is rerolled for some
reason, I think it would be a good idea to mention this in the commit
message though.
Ghanshyam Thakkar May 30, 2024, 8:39 a.m. UTC | #3
On Thu, 30 May 2024, Christian Couder <christian.couder@gmail.com> wrote:
> On Wed, May 29, 2024 at 11:41 PM Junio C Hamano <gitster@pobox.com> wrote:
> > Ghanshyam Thakkar <shyamthakkar001@gmail.com> writes:
> 
> > > +     TEST(t_add(&vars),
> > > +          "Add 2 objects, one with a non-NULL decoration and one with a NULL decoration.");
> > > +     TEST(t_readd(&vars),
> > > +          "When re-adding an already existing object, the old decoration is returned.");
> > > +     TEST(t_lookup(&vars),
> > > +          "Lookup returns the added declarations, or NULL if the object was never added.");
> > > +     TEST(t_loop(&vars), "The user can also loop through all entries.");
> >
> > These tests as a whole look like a faithful copy of the original
> > done by cmd__example_decorate().
> >
> > I do not understand the criteria used to split them into the four
> > separate helper functions.  It is not like they can be reused or
> > reordered---for example, t_readd() must be done after t_add() has
> > been done.
> >
> > What benefit are you trying to get out of these split?  IOW, what
> > are we gaining by having four separate helper functions, instead of
> > testing all of these same things in a single helper function t_all
> > with something like
> >
> >         TEST(t_all(&vars), "Do all decorate tests.");
> >

In addition to what Christian said, doing it all in one function would
provide no context as is. i.e. when we do it in a single function,

*** unit-tests/bin/t-example-decorate ***
# check "objects_noticed == 1" failed at t/unit-tests/t-example-decorate.c:46
#    left: 2
#   right: 1
# should have 2 objects
not ok 1 - All decorate tests
1..1
make[1]: *** [Makefile:78: unit-tests/bin/t-example-decorate] Error 1

vs separated

*** unit-tests/bin/t-example-decorate ***
ok 1 - Add 2 objects, one with a non-NULL decoration and one with a NULL decoration.
ok 2 - When re-adding an already existing object, the old decoration is returned.
ok 3 - Lookup returns the added declarations, or NULL if the object was never added.
# check "objects_noticed == 1" failed at t/unit-tests/t-example-decorate.c:56
#    left: 2
#   right: 1
# should have 2 objects
not ok 4 - The user can also loop through all entries.
1..4
make[1]: *** [Makefile:78: unit-tests/bin/t-example-decorate] Error 1

The latter provides much more context (we almost don't have to open
t-example-decorate.c file itself in some cases to know what failed)
than the former. Now, of course we can add more test_msg()s to the
former to improve, but I feel that this approach of splitting them
provides and improves the information provided on stdout _without_
adding any of my own test_msg()s. And I think that this is a good
middleground between cluttering the stdout vs providing very little
context while also remaining a faithful copy of the original.

> > in cmd_main()?  If there is a concrete benefit of having larger
> > number of smaller tests, would it make the result even better if we
> > split t_add() further into t_add_one() that adds one with deco_a and
> > t_add_two() that adds two with NULL?  The other helpers can of
> > course be further split into individual pieces the same way.  What
> > ere the criteria used to decide where to stop and use these four?
> 
> The original code has some kind of "sections" (or paragraphs)
> separated using comments like:
> 
>       /*
>        * Add 2 objects, one with a non-NULL decoration and one with a NULL
>        * decoration.
>        */
> 
> or:
> 
>       /*
>        * When re-adding an already existing object, the old decoration is
>        * returned.
>        */
> 
> I think it makes sense to separate the code using functions matching
> these "sections" and to reuse each comment in the TEST() macro that
> calls the corresponding function. If this patch is rerolled for some
> reason, I think it would be a good idea to mention this in the commit
> message though.

I agree about the commit message.

Thanks.
Junio C Hamano May 30, 2024, 3:54 p.m. UTC | #4
Ghanshyam Thakkar <shyamthakkar001@gmail.com> writes:

> The latter provides much more context (we almost don't have to open
> t-example-decorate.c file itself in some cases to know what failed)
> than the former. Now, of course we can add more test_msg()s to the
> former to improve, but I feel that this approach of splitting them
> provides and improves the information provided on stdout _without_
> adding any of my own test_msg()s. And I think that this is a good
> middleground between cluttering the stdout vs providing very little
> context while also remaining a faithful copy of the original.

If so, why stop at having four, each of which has more than one step
that could further be split?  What's the downside?

    Note: Here in this review, I am not necessarily suggesting the
    tests in this patch to be further split into greater number of
    smaller helper functions.  I am primarily interested in finding
    out what the unit test framework can further do to help unit
    tests written using it (i.e., like this patch).  If using
    finer-grained tests gives you better diagnosis, but if it is too
    cumbersome to separate the tests out further, is it because the
    framework is inadequate in some way?  How can we improve it?

Thanks.
Ghanshyam Thakkar June 3, 2024, 5:51 p.m. UTC | #5
On Thu, 30 May 2024, Junio C Hamano <gitster@pobox.com> wrote:
> Ghanshyam Thakkar <shyamthakkar001@gmail.com> writes:
> 
> > The latter provides much more context (we almost don't have to open
> > t-example-decorate.c file itself in some cases to know what failed)
> > than the former. Now, of course we can add more test_msg()s to the
> > former to improve, but I feel that this approach of splitting them
> > provides and improves the information provided on stdout _without_
> > adding any of my own test_msg()s. And I think that this is a good
> > middleground between cluttering the stdout vs providing very little
> > context while also remaining a faithful copy of the original.
> 
> If so, why stop at having four, each of which has more than one step
> that could further be split?  What's the downside?
> 
>     Note: Here in this review, I am not necessarily suggesting the
>     tests in this patch to be further split into greater number of
>     smaller helper functions.  I am primarily interested in finding
>     out what the unit test framework can further do to help unit
>     tests written using it (i.e., like this patch).  If using
>     finer-grained tests gives you better diagnosis, but if it is too
>     cumbersome to separate the tests out further, is it because the
>     framework is inadequate in some way?  How can we improve it?

It's not that the framework is inadequate in its current state
(for this test). As Christian said, in the original
test-example-decorate.c, the tests were divided into four sections 
by a space and comments like:
	/*
	 * Add 2 objects, one with a non-NULL decoration and one with a NULL
	 * decoration.
	 */

So, I also made those four sections in the form of those functions and
the comments became the test description. I definitely don't see any
downside in further dividing where it makes sense. For example, the
first test can be split into two, one which adds an object with non-NULL
decoration and one with NULL (I think you mentioned this). And the third
test can split to test lookup for a known object vs an unknown object.
Besides these I don't see where we can split.

Thanks.
Josh Steadmon June 3, 2024, 6:53 p.m. UTC | #6
On 2024.05.30 08:54, Junio C Hamano wrote:
> Ghanshyam Thakkar <shyamthakkar001@gmail.com> writes:
> 
> > The latter provides much more context (we almost don't have to open
> > t-example-decorate.c file itself in some cases to know what failed)
> > than the former. Now, of course we can add more test_msg()s to the
> > former to improve, but I feel that this approach of splitting them
> > provides and improves the information provided on stdout _without_
> > adding any of my own test_msg()s. And I think that this is a good
> > middleground between cluttering the stdout vs providing very little
> > context while also remaining a faithful copy of the original.
> 
> If so, why stop at having four, each of which has more than one step
> that could further be split?  What's the downside?
> 
>     Note: Here in this review, I am not necessarily suggesting the
>     tests in this patch to be further split into greater number of
>     smaller helper functions.  I am primarily interested in finding
>     out what the unit test framework can further do to help unit
>     tests written using it (i.e., like this patch).  If using
>     finer-grained tests gives you better diagnosis, but if it is too
>     cumbersome to separate the tests out further, is it because the
>     framework is inadequate in some way?  How can we improve it?

I'll try not to speak for anyone else here, but I think the test
framework isn't causing much friction here in the decision of how to
split the tests. [However, neither is it providing much guidance. At
some point we should review the unit tests and see if we can extract a
helpful style guide or best practices doc.] The setup for the cases is
minimal and done through the main function.

I think the current split is reasonable as a first patch, as it mirrors
the organization of the original test and makes it easier for reviewers
to verify that it tests the same behaviors. If further simplification or
reorganization is needed, I would like to see that as a separate patch
on top of the more straightforward conversion.

The only part that bothers me a bit (and this is really more of a
complaint about the framework than the patch itself) is the carryover of
state between the different TEST() cases. We can't skip t_add and expect
the other test cases to still pass, unfortunately. However, I don't
think this patch needs to worry about that, since the framework doesn't
restrict persistent state. [And we certainly don't restrict persistent
state in the shell tests either.]

> Thanks.
>
Ghanshyam Thakkar June 3, 2024, 9:09 p.m. UTC | #7
On Mon, 03 Jun 2024, Josh Steadmon <steadmon@google.com> wrote:
> On 2024.05.30 08:54, Junio C Hamano wrote:
> > Ghanshyam Thakkar <shyamthakkar001@gmail.com> writes:
> > 
> > > The latter provides much more context (we almost don't have to open
> > > t-example-decorate.c file itself in some cases to know what failed)
> > > than the former. Now, of course we can add more test_msg()s to the
> > > former to improve, but I feel that this approach of splitting them
> > > provides and improves the information provided on stdout _without_
> > > adding any of my own test_msg()s. And I think that this is a good
> > > middleground between cluttering the stdout vs providing very little
> > > context while also remaining a faithful copy of the original.
> > 
> > If so, why stop at having four, each of which has more than one step
> > that could further be split?  What's the downside?
> > 
> >     Note: Here in this review, I am not necessarily suggesting the
> >     tests in this patch to be further split into greater number of
> >     smaller helper functions.  I am primarily interested in finding
> >     out what the unit test framework can further do to help unit
> >     tests written using it (i.e., like this patch).  If using
> >     finer-grained tests gives you better diagnosis, but if it is too
> >     cumbersome to separate the tests out further, is it because the
> >     framework is inadequate in some way?  How can we improve it?
> 
> I'll try not to speak for anyone else here, but I think the test
> framework isn't causing much friction here in the decision of how to
> split the tests. [However, neither is it providing much guidance. At
> some point we should review the unit tests and see if we can extract a
> helpful style guide or best practices doc.] The setup for the cases is
> minimal and done through the main function.

Agreed about style guide/best practices doc.

> I think the current split is reasonable as a first patch, as it mirrors
> the organization of the original test and makes it easier for reviewers
> to verify that it tests the same behaviors. If further simplification or
> reorganization is needed, I would like to see that as a separate patch
> on top of the more straightforward conversion.
> 
> The only part that bothers me a bit (and this is really more of a
> complaint about the framework than the patch itself) is the carryover of
> state between the different TEST() cases. We can't skip t_add and expect
> the other test cases to still pass, unfortunately. However, I don't
> think this patch needs to worry about that, since the framework doesn't
> restrict persistent state. [And we certainly don't restrict persistent
> state in the shell tests either.]

I talked about this in private with Christian, and we came to the
same conclusion that having independent state would better. But seeing the
original test-example-decorate, it would be a bit more boiler plate to
produce the exact same checks, without relying on previous state. And
seeing the lack of convention (written guideline) about independent
state vs dependent, I decided to stick to having the tests rely on
previous state, similar to the original, and see the mailing list
response about what should be done.

Thanks.
diff mbox series

Patch

diff --git a/Makefile b/Makefile
index 8f4432ae57..43663fe528 100644
--- a/Makefile
+++ b/Makefile
@@ -793,7 +793,6 @@  TEST_BUILTINS_OBJS += test-dump-fsmonitor.o
 TEST_BUILTINS_OBJS += test-dump-split-index.o
 TEST_BUILTINS_OBJS += test-dump-untracked-cache.o
 TEST_BUILTINS_OBJS += test-env-helper.o
-TEST_BUILTINS_OBJS += test-example-decorate.o
 TEST_BUILTINS_OBJS += test-example-tap.o
 TEST_BUILTINS_OBJS += test-find-pack.o
 TEST_BUILTINS_OBJS += test-fsmonitor-client.o
@@ -1335,6 +1334,7 @@  THIRD_PARTY_SOURCES += sha1collisiondetection/%
 THIRD_PARTY_SOURCES += sha1dc/%
 
 UNIT_TEST_PROGRAMS += t-ctype
+UNIT_TEST_PROGRAMS += t-example-decorate
 UNIT_TEST_PROGRAMS += t-mem-pool
 UNIT_TEST_PROGRAMS += t-prio-queue
 UNIT_TEST_PROGRAMS += t-strbuf
diff --git a/decorate.h b/decorate.h
index cdeb17c9df..08af658d34 100644
--- a/decorate.h
+++ b/decorate.h
@@ -3,7 +3,7 @@ 
 
 /*
  * A data structure that associates Git objects to void pointers. See
- * t/helper/test-example-decorate.c for a demonstration of how to use these
+ * t/unit-tests/t-example-decorate.c for a demonstration of how to use these
  * functions.
  */
 
diff --git a/t/helper/test-example-decorate.c b/t/helper/test-example-decorate.c
deleted file mode 100644
index 8f59f6be4c..0000000000
--- a/t/helper/test-example-decorate.c
+++ /dev/null
@@ -1,78 +0,0 @@ 
-#include "test-tool.h"
-#include "git-compat-util.h"
-#include "object.h"
-#include "decorate.h"
-#include "repository.h"
-
-int cmd__example_decorate(int argc UNUSED, const char **argv UNUSED)
-{
-	struct decoration n;
-	struct object_id one_oid = { {1} };
-	struct object_id two_oid = { {2} };
-	struct object_id three_oid = { {3} };
-	struct object *one, *two, *three;
-
-	int decoration_a, decoration_b;
-
-	void *ret;
-
-	int i, objects_noticed = 0;
-
-	/*
-	 * The struct must be zero-initialized.
-	 */
-	memset(&n, 0, sizeof(n));
-
-	/*
-	 * Add 2 objects, one with a non-NULL decoration and one with a NULL
-	 * decoration.
-	 */
-	one = lookup_unknown_object(the_repository, &one_oid);
-	two = lookup_unknown_object(the_repository, &two_oid);
-	ret = add_decoration(&n, one, &decoration_a);
-	if (ret)
-		BUG("when adding a brand-new object, NULL should be returned");
-	ret = add_decoration(&n, two, NULL);
-	if (ret)
-		BUG("when adding a brand-new object, NULL should be returned");
-
-	/*
-	 * When re-adding an already existing object, the old decoration is
-	 * returned.
-	 */
-	ret = add_decoration(&n, one, NULL);
-	if (ret != &decoration_a)
-		BUG("when readding an already existing object, existing decoration should be returned");
-	ret = add_decoration(&n, two, &decoration_b);
-	if (ret)
-		BUG("when readding an already existing object, existing decoration should be returned");
-
-	/*
-	 * Lookup returns the added declarations, or NULL if the object was
-	 * never added.
-	 */
-	ret = lookup_decoration(&n, one);
-	if (ret)
-		BUG("lookup should return added declaration");
-	ret = lookup_decoration(&n, two);
-	if (ret != &decoration_b)
-		BUG("lookup should return added declaration");
-	three = lookup_unknown_object(the_repository, &three_oid);
-	ret = lookup_decoration(&n, three);
-	if (ret)
-		BUG("lookup for unknown object should return NULL");
-
-	/*
-	 * The user can also loop through all entries.
-	 */
-	for (i = 0; i < n.size; i++) {
-		if (n.entries[i].base)
-			objects_noticed++;
-	}
-	if (objects_noticed != 2)
-		BUG("should have 2 objects");
-
-	clear_decoration(&n, NULL);
-
-	return 0;
-}
diff --git a/t/helper/test-tool.c b/t/helper/test-tool.c
index f6fd0fe491..2d82515f56 100644
--- a/t/helper/test-tool.c
+++ b/t/helper/test-tool.c
@@ -29,7 +29,6 @@  static struct test_cmd cmds[] = {
 	{ "dump-split-index", cmd__dump_split_index },
 	{ "dump-untracked-cache", cmd__dump_untracked_cache },
 	{ "env-helper", cmd__env_helper },
-	{ "example-decorate", cmd__example_decorate },
 	{ "example-tap", cmd__example_tap },
 	{ "find-pack", cmd__find_pack },
 	{ "fsmonitor-client", cmd__fsmonitor_client },
diff --git a/t/helper/test-tool.h b/t/helper/test-tool.h
index 868f33453c..bc334183c3 100644
--- a/t/helper/test-tool.h
+++ b/t/helper/test-tool.h
@@ -23,7 +23,6 @@  int cmd__dump_split_index(int argc, const char **argv);
 int cmd__dump_untracked_cache(int argc, const char **argv);
 int cmd__dump_reftable(int argc, const char **argv);
 int cmd__env_helper(int argc, const char **argv);
-int cmd__example_decorate(int argc, const char **argv);
 int cmd__example_tap(int argc, const char **argv);
 int cmd__find_pack(int argc, const char **argv);
 int cmd__fsmonitor_client(int argc, const char **argv);
diff --git a/t/t9004-example.sh b/t/t9004-example.sh
deleted file mode 100755
index 590aab0304..0000000000
--- a/t/t9004-example.sh
+++ /dev/null
@@ -1,12 +0,0 @@ 
-#!/bin/sh
-
-test_description='check that example code compiles and runs'
-
-TEST_PASSES_SANITIZE_LEAK=true
-. ./test-lib.sh
-
-test_expect_success 'decorate' '
-	test-tool example-decorate
-'
-
-test_done
diff --git a/t/unit-tests/t-example-decorate.c b/t/unit-tests/t-example-decorate.c
new file mode 100644
index 0000000000..3c856a8cf2
--- /dev/null
+++ b/t/unit-tests/t-example-decorate.c
@@ -0,0 +1,80 @@ 
+#include "test-lib.h"
+#include "object.h"
+#include "decorate.h"
+#include "repository.h"
+
+struct test_vars {
+	struct object *one, *two, *three;
+	struct decoration n;
+	int decoration_a, decoration_b;
+};
+
+static void t_add(struct test_vars *vars)
+{
+	void *ret = add_decoration(&vars->n, vars->one, &vars->decoration_a);
+
+	if (!check(ret == NULL))
+		test_msg("when adding a brand-new object, NULL should be returned");
+	ret = add_decoration(&vars->n, vars->two, NULL);
+	if (!check(ret == NULL))
+		test_msg("when adding a brand-new object, NULL should be returned");
+}
+
+static void t_readd(struct test_vars *vars)
+{
+	void *ret = add_decoration(&vars->n, vars->one, NULL);
+
+	if (!check(ret == &vars->decoration_a))
+		test_msg("when readding an already existing object, existing decoration should be returned");
+	ret = add_decoration(&vars->n, vars->two, &vars->decoration_b);
+	if (!check(ret == NULL))
+		test_msg("when readding an already existing object, existing decoration should be returned");
+}
+
+static void t_lookup(struct test_vars *vars)
+{
+	void *ret = lookup_decoration(&vars->n, vars->one);
+
+	if (!check(ret == NULL))
+		test_msg("lookup should return added declaration");
+	ret = lookup_decoration(&vars->n, vars->two);
+	if (!check(ret == &vars->decoration_b))
+		test_msg("lookup should return added declaration");
+	ret = lookup_decoration(&vars->n, vars->three);
+	if (!check(ret == NULL))
+		test_msg("lookup for unknown object should return NULL");
+}
+
+static void t_loop(struct test_vars *vars)
+{
+	int i, objects_noticed = 0;
+
+	for (i = 0; i < vars->n.size; i++) {
+		if (vars->n.entries[i].base)
+			objects_noticed++;
+	}
+	if (!check_int(objects_noticed, ==, 2))
+		test_msg("should have 2 objects");
+}
+
+int cmd_main(int argc UNUSED, const char **argv UNUSED)
+{
+	struct object_id one_oid = { { 1 } }, two_oid = { { 2 } }, three_oid = { { 3 } };
+	struct test_vars vars = { 0 };
+
+	vars.one = lookup_unknown_object(the_repository, &one_oid);
+	vars.two = lookup_unknown_object(the_repository, &two_oid);
+	vars.three = lookup_unknown_object(the_repository, &three_oid);
+
+	TEST(t_add(&vars),
+	     "Add 2 objects, one with a non-NULL decoration and one with a NULL decoration.");
+	TEST(t_readd(&vars),
+	     "When re-adding an already existing object, the old decoration is returned.");
+	TEST(t_lookup(&vars),
+	     "Lookup returns the added declarations, or NULL if the object was never added.");
+	TEST(t_loop(&vars), "The user can also loop through all entries.");
+
+	clear_decoration(&vars.n, NULL);
+
+	return test_done();
+}