Advanced C Programming

Fall 2022 ECE 264 :: Purdue University :: Section 3 (Quinn)

⚠ This is a PAST SEMESTER (Fall 2022).
Due 10/2

Unit testing

Learning goals

You will learn the following concepts/skills:

  1. unit testing – systematically test your code
    1. framework – create a simple framework for unit-testing future C code
    2. organization – learn how to organize a test suite
    3. test coverage – measure how thoroughly your tests exercise your code
  2. C preprocessor − practice using #define symbols and macros with #ifdef.

Overview

Real-world software projects typically comprise tens, hundreds, or many thousands of files. Even in your ECE courses, your programming assignments will be getting bigger and more complex. Testing code by hand (i.e., by playing with it) is useless since there are so many components, and ways things can go wrong. Hand-testing effectively would be complex—and prohibitively slow.

Until now, we have used a very low-tech method of structuring our tests with the expected.txt and the diff command. That method is based on the more general principle of unit testing.

In unit testing, programmers write collections of small functions that test some aspect of a program. For example, in mintf(…) in HW05, you might have a unit test for small positive integers in base 10, another unit for small negative numbers, one for extreme bases, extreme positive values, extreme negative values, and so on. Each test is its own function.

As the number and complexity of tests grows, it becomes necessary to have a foundation of code for running and organizing them. This is called a unit test framework. There are unit test frameworks for every major programming language. For C, one example is CUnit. These typically have special-purpose macros for checking assumptions, similarly to the assert(…) macro, which we have already covered this semester, but with a unit test framework, we may allow the code to continue, even if an assumption is not met, since the goal is simply to check. In addition to the checking macros, real-world testing frameworks have mechanisms for setting up the needed environment that the program-in-test depends on (e.g., files, network connections, etc.), and sophisticated interfaces for visualizing the results and scheduling test runs.

In this homework, you will create a very simple unit test framework… let's call it a unit test library. You may use this to test future assignments. We will illustrate by example.

Using miniunit.h to test — minimal example

Suppose you have the following (very trivial) module for doing arithmetic calculations involving the number 3.

three.c
 1 #include "three.h"
 2
 3 int times_3(int n) {
 4     return n * 3;
 5 }
 6
 7 int divided_by_3(int n) {
 8     return n % 3;  // BUG
 9 }
10
11 int plus_3(int n) {
12     return n * 3;  // BUG
13 }
14
15 int minus_3(int n) {
16     return n - 3;
17 }
three.h
 1 int times_3(int n);
 2 int divided_by_3(int n);
 3 int plus_3(int n);
 4 int minus_3(int n);

Here is the general format for every unit test function (e.g., in test_three.c). You will typically have several of these in your test_▒▒▒.c.

int test_▒▒▒() {
    mu_start();  // set up the test
    // …
    mu_check(condition);
    // …
    mu_check(condition);
    // …
    mu_end();    // finish the test
}

Then, to call all of your unit test functions, you will have a runner function (e.g., main(…)) like this:

int main(int argc, char* argv[]) {
    mu_run(test_▒▒▒);
    mu_run(test_▒▒▒);
    mu_run(test_▒▒▒);
    mu_run(test_▒▒▒);
    return EXIT_SUCCESS;
}

Using the above format, we create the following unit test suite (group of tests) for our three-arithmetic module.

// test_three.c

#include <stdio.h>
#include <stdlib.h>
#include "miniunit.h"
#include "three.h"

int test_plus_3() {
    mu_start();
    mu_check(plus_3(1) == 4);  // will FAIL
    mu_end();
}

int test_minus_3() {
    mu_start();
    mu_check(minus_3(1) == -2);
    mu_end();
}

int test_times_3() {
    mu_start();
    mu_check(times_3(1) == 3);
    mu_end();
}

int test_divided_by_3() {
    mu_start();
    mu_check(divided_by_3(1) == 0);  // will FAIL
    mu_end();
}

int main(int argc, char* argv[]) {
    mu_run(test_plus_3);        // will FAIL
    mu_run(test_minus_3);
    mu_run(test_times_3);
    mu_run(test_divided_by_3);  // will FAIL
    return EXIT_SUCCESS;
}

Your unit test library, using #define macros will convert that seemingly simple test code into a colored summary, including the name of the test function and the line where it failed. Here is the expected out put for the test above:

$ gcc -o test_three three.c test_three.c

$ ./test_three
Test failed: test_plus_3 at line 8
Test passed: test_minus_3
Test passed: test_times_3
Test failed: test_divided_by_3 at line 26

$

1. Set up your homework directory.

You will be creating a file called miniunit.h from scratch (i.e., starting with a blank file).

The only starter code is a (deliberately) buggy program called . You will get two files: count_words.c and count_words. You will use your miniunit.h to write a test file (test_count_words.c) for testing . This will let you verify that your miniunit.h is working, and practice using it.

Get the starter code and cd into your HW07 directory.

you@ecegrid-thin1 ~ $ cd 264
you@ecegrid-thin1 ~/264 $ 264get hw07
This will write the following files: 1. hw07/count_words.c 2. hw07/count_words.h Ok? (y/n)y 2 files were written
you@ecegrid-thin1 ~/264 $ cd hw07
you@ecegrid-thin1 ~/264/hw07 $

2. Create your miniunit.h.

Create a file called miniunit.h from scratch. Then, create the five #define macros as specified in the Requirements table.

you@ecegrid-thin1 ~/264/hw07 $ vim miniunit.h

3. Use miniunit.h to improve test_count_words.c

This part will teach you how to use miniunit.h in practice for future assignments. We are giving you the code for free, so it shouldn't take more than about 15 minutes to type and test.

  1. At the top of test_count_words.c, include miniunit.h.
  2. Add three test functions above main(…): int _test_empty(), int _test_simple(), and int _test_hard().
  3. Inside each test function, add mu_start() at the beginning, and mu_end(…) just before the end.
  4. mu_check(…) should be invoked at least once in between mu_start(…) and mu_end(…).
  5. At the top of main(…), add mu_run(_test_empty), mu_run(_test_simple), and mu_run(_test_hard).
  6. The result may look something like this:
    #include <stdio.h>
    #include <stdlib.h>
    #include "count_words"
    #include "miniunit.h"
    #include "log_macros.h"
    
    static int _test_empty() {
      mu_start();
      //-------------------------------
      mu_check(count_words("") == 0);
      //-------------------------------
      mu_end();
    }
    
    static int _test_simple() {
      mu_start();
      //-------------------------------
      mu_check(count_words("apple") == 1);
      mu_check(count_words("boring boxes") == 2);
      mu_check(count_words("apple banana") == 2);
      mu_check(count_words("apple banana cherry") == 3);
      //-------------------------------
      mu_end();
    }
    
    static int _test_hard() {
      mu_start();
      //-------------------------------
      mu_check(count_words("famigerate fiddle-faddle") == 2);
      mu_check(count_words("Mary's mongoose") == 2);
      mu_check(count_words("plumbers' platitudes") == 2);
      //-------------------------------
      mu_end();
    }
    
    int main(int argc, char* argv[]) {
      mu_run(_test_empty);
      mu_run(_test_simple);
      mu_run(_test_hard);
    
      log_int(count_words("My apples are sweet."));
      log_int(count_words("My friend's apples are sweeter."));
      log_int(count_words("A pear is sweeter than an apple.."));
    
      return EXIT_SUCCESS;
    }
    
    Note: Due to the intentially planted bugs in count_words.c, _test_simple(…) will fail on "apple banana" (due to 'a'), and _test_hard(…) will fail on "familgerate fiddle-faddle" (due to '-').
  7. Reminder: count_words(…) has intentionally planted bugs. It is okay that some of the results are obviously incorrect and the tests fail. The purpose of tests is to detect flaws. If the flaws are detected, then the test worked correctly.
    We are giving you some code for free. You are welcome to make changes, or hand-copy as is, as long as you understand what it is doing. If not, please ask.
  8. You must submit your test_count_words.c, even if it is identical to the code above.

Printing in color

To print in color on the console—i.e., in an SSH window or terminal window—we use ANSI codes. These are instructions—mixed in with the text you want to display—that tell the terminal how to display the text.

Let's look at one ANSI code. To tell the terminal to start printing in green, you send "\x1b[31m".

By “send,” we really just mean print. So you could start printing in green using this function call:

printf("\x1b[31m")

After that, everything you print will be in green.

Let's break down that ANSI code to see what the characters mean.

\x1b is the escape character. It has ASCII value 27 (hexadecimal: 0x1b). In C, you can specify any character by its ASCII value as "\x▒▒", where ▒▒ is the ASCII value in expressed in hexadecimal. Sending the escape character ("\x1b") is equivalent to pressing the escape key on a keyboard. Every ANSI code must begin with the escape character.

[32m is a command that means, “set the foreground text color to green.” Using other numbers, you can set the foreground color to other colors, or set the background color and other aspects of the text formatting.

Color Foreground Background
black 30 40
red 31 41
green 32 42
yellow 33 43
blue 34 44
magenta 35 45
cyan 36 46
white 37 47

For example, to start printing all text in red, you would use this:

printf("\x1b[31m")

To reset (i.e., go back to the default colors), do the same thing, but with 0, like this:

printf("\x1b[0m")

You can put these all together in one call to printf(…). For example, …

printf("Roses are \x1b[31mRED\x1b[0m.\n");     // "\x1b[31m"→red;  "\x1b[0m"→reset.
printf("Violets are \x1b[34mBLUE\x1b[0m.\n");  // "\x1b[34m"→blue

… would print …

Roses are RED.
Violets are BLUE.

That code can be more readable if you put each code in a parameter of the printf(…) like this:

char* ansi_red   = "\x1b[31m";
char* ansi_blue  = "\x1b[34m";
char* ansi_reset = "\x1b[0m";
printf("Roses are %sRED%s.\n", ansi_red, ansi_reset);
printf("Violets are %sBLUE%s.\n", ansi_blue, ansi_reset);

Copying from this page

You may hand-copy any code snippets you find in this homework description into your HW07 submission.

Copying from this page is not necessary. We are allowing this only because there is some unfamiliar syntax, and we think this flexibility may help you. You are responsible for checking and adapting anything you copy, and ensuring the proper functioning of the code you submit.

Do not use copy-paste. You learn more from hand-copying unfamiliar syntax. Expect problems if you ignore this.

How much work is this?

partially redacted screenshot of solution

Bonus options

Bonus #1: Selective testing (2 bonus points)

Allow the user (probably you) to run just one test function at a time by passing the name of that test function on the command line.

Example: The test_count_words.c code above includes three test functions: _test_empty(…), _test_simple(…), and _test_hard(…). Let's assume it has been compiled to an executable file called test_count_words with a completed miniunit.h, including this bonus option. Running… ./test_count_words _test_empty (from bash) would run all of three test functions.

you@ecegrid-thin1 ~/264/hw07 $ ./test_count_words _test_simple

… would run only the _test_simple(…) function. The others would be skipped.

Running the executable without any command line arguments (i.e., ./test_count_words) would still run all of the test functions, just like before.

To implement this bonus option, you will need to use argv, argc, and strcmp(…). argv is an array of all of the command line arguments that were passed when the program was run. The name of the executable is the first item. argc is the number of strings in that array.

In the example above (./test_count_words _test_simple), argc would be 2, argv[0] would be "./test_count_words", and argv[1] would be "_test_simple".

Add the following line to your miniunit.h to indicate that you have completed Bonus 1.

#define MINIUNIT_BONUS_SELECTIVE_TESTING

Check this carefully. Without the MINIUNIT_BONUS_SELECTIVE_TESTING, you will not get the bonus points.

Bonus #2: Inline functions (1 bonus point)

Implement everything in your miniunit.h (including Bonus #1 only if you are doing that option), but for the macros that expand to multiple statements, instead of using a do…while(false) block, call a helper function declared and defined right in your miniunit.h. The helper function must be declared as inline static and the name must begin with

  • Helper functions must be declared as inline static. That avoids some linker errors that normally happen if you try to define a function inside a header file.
  • Helper function names must be named _mu_check(…) (helper for mu_check(…) macro) and _mu_run(…) (helper for mu_run(…) macro). You should not need any other helper functions.
  • All #define macros should be only one line of code (and containing no semicolons or curly braces).
  • You should have no do…while(false) blocks.

Do not include function definitions in any header file, unless we have given you explicit instructions to do so. It is allowed only for this bonus.

Defining a function in a header file is extremely unusual. Normally, a header file contains only function declarations (i.e., function signature only), not definitions (i.e., body of function). It is only done in very rare circumstances. We are instructing you to do this very unusual thing for this bonus because it allows you to use your miniunit.h in the same way you would if they were just macros, because the amount of code is very small, and some of the other issues that could arise with function definitions in header files do not apply here.

Working on bonuses

Bonuses are open to anybody who wants to try them. For some students, they are an extra challenge to keep building coding skills. For other students, they are a way to pick up some points to improve their grade in case they stumbled with a previous assignment (or for insurance against the future). Either way, bonuses typically require a bit more independence than the rest of the assignment. TAs will help, but some may less familiar with the specifics.

Do not work on bonuses until you are finished with the main assignment. It will not be more efficient.

#define MINIUNIT_BONUS_INLINE_FUNCTIONS

Check this carefully. Without the MINIUNIT_BONUS_INLINE_FUNCTIONS, you will not get the bonus points.

How bonus points work

For Fall 2022, bonus points are worth 1% (per bonus point) toward your final grade (bottom line). Thus, doing both of these bonuses would add 3% toward your bottom line at the end of the semester.

Submit your bonus along with your regular submission.

Bonuses are scored separately from regular assignments, and usually much later (possibly even at the end of the semester). There is no partial credit on bonuses. In our experience, it is almost never an issue. Bonuses are designed to be either done or not done. If you think you're done with a bonus and you've made some reasonable effort to check it, you will almost certainly get credit. can check your bonus for you in office hours if you wish.

Depending on the volume we receive, bonuses might be hand-checked.

There is no late penalty on bonuses, as long as they are received by the last late deadline. (See the syllabus.) We encourage you to finish everything—including bonuses—by the regular deadline. However, the net effect of this policy is that you can conceivably submit a bonus any time up to the last late deadline for an assignment and still get the full bonus points.

Requirements

  1. Your submission must contain each of the following files, as specified:
    file contents
    miniunit.h symbols
    MU SUCCESS

    Use #define to define a symbolic constant called MU_SUCCESS that expands to 0.

    Copy this code (by hand). This is all you need for this part.

    #define MU_SUCCESS 0

    This will be used in the other macro definitions to indicate that no error has been detected. Although they could have been implemented using just 0, using a symbolic constant makes the code more clear. As a rule, whereever possible, you should only use 0 in your code if you are referring to zero of something (e.g., 0 bananas, 0-length string, 0 errors, etc.).

    macros
    mu start()
    1. Expand to code that declares a local variable called __mu_first_failure_line_number_or_0 and initializes it to MU_SUCCESS (equals 0). That signifies that so far, no tests have failed.
    2. The purpose of __mu_first_failure_line_number_or_0 is to store the line number of the first "check". (i.e., invocation of mu_check(…)) that failed (if any). If no checks fail, then __mu_first_failure_line_number_or_0 will still be equal to MU_SUCCESS when the test function returns.
    3. Initial value of this variable should be MU_SUCCESS (equals 0).
    4. Tip: mu_start(…) will be only one line; there should be no semicolon.
    mu check(condition)
    1. If condition is false and no other checks (i.e., calls to mu_check(…)) have failed, yet, then store the line number of this mu_check(…) invocation in the variable that you created in mu_start(…).
      • Store the line number only on the first check that fails.
      • If you have multiple calls to mu_check(…) that fail, you must keep only the line number of the first one that failed.
      • If condition is a function call, invoking mu_check(…) must not result in calling that function multiple times.
      • Remember: Invoking a macro is not the same as calling a function. The macro expands to code that will be executed. Macros are just text manipulation on your code, even though sometimes they may feel like functions.
    2. You can get the current line number with __LINE__.
    3. Tip: Expect ≈6 lines of code for a typical, well-formatted mu_check(…) macro definition.
    mu run(function)
    • Expand to code that calls function() (with no parameters).
    • If it returns 0, then print (in green):
      Test passed: function
    • If it returns a line number (≥1), then print (in red):
      Test failed: function at line line#
    • Tip: Expect ≈10-13 lines of code for well-formatted mu_run(…) macro definition.
    mu end()
    Expand to code that returns the line number of the first check (mu_check(…)) that failed, or 0 if all checks succeeded.
    • Hint: This will be just one line; there should be no semicolon.
    • If you have more than one line, you probably made a mistake.
    mu check strings equal(s1, s2)
    Expand to code that checks checks that two strings (s1 and s2) are equal.
    • Follow the same rules as for mu_check(…).
    • Invoking mu_check_strings_equal(…) should invoke mu_check(…).
    • Resulting code should use strcmp(…) to check that the two strings (s1 and s2) are equal.
    • This is just a wrapper for mu_check(…) to make it easier to compare strings.
    • Invoking mu_check_strings_equal("A", "A") should generate the same C code as mu_check(strcmp("A", "A") == 0)).
    • Hint: This will be just one line; there should be no semicolon.
    • If you have more than one line, you probably made a mistake.
    • The code generated by mu_check(…) should not print anything.
    test_count_words.c
    (as described in part 3 above)
  2. You may hand-copy any code snippets you find in this homework description into your HW07 submission.
    • Do not use copy-paste. You learn more from hand-copying unfamiliar syntax. Expect problems if you ignore this.
    • Be sure you understand what you are copying. You are responsible for checking and adapting anything you use, and ensuring the correct functioning of whatever code you submit.
    • Copying from this page is not necessary. This permission is given as a convenience, since some of the syntax may be unfamiliar, and this homework is more tightly specified than most others.
    • This permission applies only to this homework (HW07) in this semester (Fall 2022).
  3. You may use any of the following:
    header functions/symbols allowed in…
    stdbool.h bool, true, false *.c, *.h
    stdio.h printf, fputs, fprintf *.c, *.h
    string.h strcmp test_count_words.c, miniunit.h
    stdlib.h EXIT_SUCCESS test_count_words.c
    Check with the instructor if you wish to use others. If you find yourself in need of something else, there's a decent chance you are on the wrong track.
  4. Submissions must meet the code quality standards and the course policies on homework and academic integrity.
  5. Macros containing more than one statement must use a do-while loop, and be indented similar regular C code, like this:
    #define print_repeated(msg, num_repetitions)       \
        do {                                           \
            for(int i = 0; i < num_repetitions; i++) { \
                printf("%s\n", (msg));                 \
            }                                          \
        } while(false)
    
    • First line: #define name() \
    • Second line: do { \ indented with 1 tab (or 4 spaces); ends with a '\'
    • Additional lines: body of your macro each line indented with 2 tabs (or 8 spaces); ends with '\'
    • Last line: while(false) indented with 1 tab (or 4 spaces); no ending '\'
    • After last line: Leave at least one blank line
  6. Indent your macros similarly to regular C code. Your code must be readable.

Submit

To submit HW07 from within your hw07 directory, type 264submit HW07 miniunit.h test_count_words.c

Pre-tester

The pre-tester for HW07 has not yet been released. As soon as it is ready, this note will be changed.

Q&A

  1. Should I have a semicolon at the end of the RHS of a #define macro

    No. The person using the macro will normally include the semicolon.
  2. How can I continue the RHS of a #define macro definition onto the next line

    Put a backslash ("\") at the end of the line. (But keep reading for more on how to do this right.)
    #define profess_love_for_food(food) printf("I love %s", \
            food)
  3. Can I have a macro with multiple C statements

    Yes. In theory, you could just have the two statements separated by a semicolon like this:
    // BAD
    #define profess_love_for_two_foods(food1, food2) \
          printf("I love %s", food1);                \
          printf("I love %s", food2)
    However, that would lead to surprising results if someone who doesn't follow the code quality standards calls that macro in an if statement like this:
    if(1 == 0)
        profess_love_for_two_foods("soap", "poison");
    Only the first statment would be connected to the if statement.
    if(1 == 0)
        printf("I love %s", "soap");
    
    printf("I love %s", "poison");
    It is tempting to just put curly braces around the two statements, but that also causes problems.
    // BAD
    #define profess_love_for_two_foods(food1, food2) { \
        printf("I love %s", food1); \
        printf("I love %s", food2); \
    }

    The problem comes back to uncivilized oafs who write if statements without curly braces, like this:

    if(age >= 30)
        profess_love_for_two_foods("chocolate", "pizza");
    else
        profess_love_for_two_foods("spinach", "broccoli");

    The above example would result in this:

    if(age >= 30) {
        printf("I love %s", "chocolate");
        printf("I love %s", "pizza");
    };  ← PROBLEM
    else {
        printf("I love %s", "spinach");
        printf("I love %s", "broccoli");
    };
    The standard solution is to wrap the statements in a do { … } while(false) block. Because the do…while requires a semicolon, this actually works out like we want.
    Yes, it is ugly. Hacks like this are not something the instructor would normally condone, but it is standard practice because there are very few truly versatile options for this.
    // USE THIS WAY
    #define profess_love_for_two_foods(food1, food2) do { \
        printf("I love %s", food1);                       \
        printf("I love %s", food2);                       \
    } while(false)
  4. Why does miniunit.h need mu_start(…) and mu_end(…)

    These give a relatively clean and consistent form to your unit tests, so you can focus on the code that matters for each particular test.
    Without mu_start(…) and mu_end(…), one might resort to a naïve approach, like this:
    YUK!
    int test_count_words_▒▒▒▒▒() { int __mu_first_failure_line_number_or_0 = __MU_SUCCESS; mu_check(▒▒▒▒▒▒▒▒▒▒); mu_check(▒▒▒▒▒▒▒▒▒▒); mu_check(▒▒▒▒▒▒▒▒▒▒); return __mu_first_failure_line_number_or_0; }
    The programmer should not have to know the specific internal names used by miniunit.h! If they get it wrong, then it won't work properly with mu_check(…).
    Of course, they could avoid the problem of consistency if they do the whole thing without any macros—i.e., without miniunit.h—but then the above skeleton would be even messier!
    YUK! YUK! YUK! YUK! YUK!
    int test_count_words_▒▒▒▒▒() { const int __MU_SUCCESS = 0; int __mu_first_failure_line_number_or_0 = __MU_SUCCESS; if(!(▒▒▒▒▒▒▒▒▒▒) && __mu_first_failure_line_number_or_0 == __MU_SUCCESS) { __mu_first_failure_line_number_or_0 = __LINE__; } if(!(▒▒▒▒▒▒▒▒▒▒) && __mu_first_failure_line_number_or_0 == __MU_SUCCESS) { __mu_first_failure_line_number_or_0 = __LINE__; } if(!(▒▒▒▒▒▒▒▒▒▒) && __mu_first_failure_line_number_or_0 == __MU_SUCCESS) { __mu_first_failure_line_number_or_0 = __LINE__; } return __mu_first_failure_line_number_or_0; }
    With mu_start(…) and mu_end(…)—and adding a divider comment (optional)—you get this:
    GOOD
    int test_count_words_▒▒▒▒▒() { mu_start(); //──────────────────────────────────────── mu_check(▒▒▒▒▒▒▒▒▒▒); mu_check(▒▒▒▒▒▒▒▒▒▒); mu_check(▒▒▒▒▒▒▒▒▒▒); //──────────────────────────────────────── mu_end(); }
    mu_start(…) and mu_end(…) also make your unit testing library more extensible. If you wanted to change how your tests are organized and/or reported, you could do so without changing all of your test code that uses it.
  5. What does #x do in a #define macro

    It expands to the text of the expression, instead of its value. This is easiest to see if you test using the /usr/bin/cpp command.
    Here is an example, which uses the log_int(…) snippet given in the Requirements table.
    // demonstrate_hash.c
    #include <stdio.h>
    #include <stdlib.h>
    
    #define log_int(n) printf("%s == %d\n", (#n), (n))
    
    int main(int argc, char* argv[]) {
        log_int(3 + 3);
        return EXIT_SUCCESS;
    }
    
    If we process that with the preprocessor directly (instead of via gcc), we can see what it becomes.
    you@ecegrid-thin1 ~/HW07 $ /usr/bin/cpp demonstrate_hash.c -DDEBUG | indent -kr
    … int main(int argc, char* argv[]) { printf("%s == %d\n", ("3 + 3"), (3 + 3)); return 0; }
    Notice that the third argument to fprintf(…) is a string literal, "3 + 3"—the text of the argument that was passed to log_int(…). That is different from the fourth argument, which is the value of that parameter, 3 + 3 (= 6).
  6. How do I make mu_run(…) print the success/failure messages in color?

    Define a helper macro for printing in color outside the #ifdef/#endif. You can use the one in the Q&A of miniunit_1 or make your own. You will need to define the ANSI codes (#define ANSI_RED …, etc.) outside the #ifdef/#endif, as well.
  7. GCC: “error: ‘true’ undeclared” ⋯???
    GCC: “error: ‘false’ undeclared” ⋯??

    The true and false constants are defined in a standard header called stdbool.h; you need to include it (i.e., #include <stdbool.h>) in any file where you use them. This was communicated in the Requirements table, as well as the Code Quality Standards, which have more about bool, true, and false.
  8. GCC: “__mu_first_failure_line_number_or_0 is not defined” ⋯ Why

    If you get that within main(…), make sure you are not trying to use the variable declared in mu_start(…) in your mu_run(…). Your mu_start(…) is expanded in the context of a _test_▒▒▒(…) function, while mu_run(…) is expanded in the context of main(…).
  9. CPP: "Unexpected EOF (end-of-file)" (or similar). Why?

    You probably have unmatched braces somewhere in your miniunit.h. First, fix your indents. It will make the errors more obvious. Then, be sure to use /usr/bin/cpp test_count_words.c | indent -kr or /usr/bin/cpp test_count_words.c -DDEBUG | indent -kr to make the output readable.
  10. How do I start? Can I use TDD?

    Try this progression:
    1. Make it work for an empty test function (i.e., no calls to mu_check(…)).
    2. Make it work for tests that always pass. In other words, assume the condition is true.
    3. Make it work for test functions with one test that fails.
    4. Make it work for test functions with any number of tests that fail.

Updates

9/29/2022 Use 264get hw07 to get the starter code, which includes count_words.c and count_words.