Home > database >  How do I initialize a function as every combination of a set of (Boolean) flags? (C )
How do I initialize a function as every combination of a set of (Boolean) flags? (C )

Time:10-29

Im doing unit testing and want to test my functions against each possible combinations of flags. Each flag can be either a 1 or a 0. the flags are global variables.

This is what I tried:

`

for(flagA = 0; flagA <= 1;   flagA){
    for(flagB = 0; flagB <= 1;   flagB){
        for(flagC = 0; flabC <= 1;   flagC){
            for(flagD = 0; flagD <= 1;   flagD){
                myFunction();
            }
        }
    }
};

`

The issue I am having is myFunction() can change a flags value, causing the other calls of myFunction() to be called with unexpected flag values rather than testing every combination.

CodePudding user response:

Using your nested loop approach, you could do that with a slight tweak to insure all your variables get reset before invoking the next test case:

for(int a = 0; a <= 1;   A){
    for(int b = 0; b <= 1;   b){
        for(int c = 0; c <= 1;   c){
            for(int d = 0; d <= 1;   d){
                flagA = a;
                flagB = b;
                flagC = c;
                flagD = d;
                myFunction();
            }
        }
    }
};

Of if you have a lot more flags, you could do a table approach with some bit shifting. If you have N flags, there's 2ᴺ possible combinations.

You could do something like this with a table of pointers to all your global flag variables.


void YourTestFunction()
{

    int* flagArray[] = {&flagA, &flagB, &flagC, &flagD, &flagE, &flagF};
    size_t flagArraySize = sizeof(flagArray)/sizeof(flagArray[0]);
    size_t testCount = 1;

    // compute 2ᴺ
    for (size_t i = 0; i < flagArraySize; i  )
    {
       testCount *= 2;
    }

    for (size_t i = 0; i < testCount; i  )
    {
       for (size_t j = 0; j < flagArraySize; j  )
       {
           *flagArray[j] = ((i >> j) & 0x01) ? 1 : 0;
       }
       myFunction();
    }


CodePudding user response:

Don't use global variables and avoid functions with side-effects mutating global state. They are hard to test (as you have found out) and can introduce hard to detect bugs.

Change your function signature to receive the flags on which it operates:

for(flagA = 0; flagA <= 1;   flagA){
    for(flagB = 0; flagB <= 1;   flagB){
        for(flagC = 0; flabC <= 1;   flagC){
            for(flagD = 0; flagD <= 1;   flagD){
                myFunction(flagA, flagB, flagC, flagD);
            }
        }
    }
}

If you cannot change the function, then make sure to assign the global flags to the required values directly before calling the function and use different variables as loop counters:

for(auto a = 0; a <= 1;   a){
    for(auto b = 0; b <= 1;   b){
        for(auto c = 0; c <= 1;   c){
            for(auto d = 0; d <= 1;   d){
                flagA = a;
                flagB = b;
                flagC = c;
                flagD = d;
                myFunction();
            }
        }
    }
}

Since a boolean can hold true/1 and false/0, your loop generates the flag patterns:

abcd
----
0000
0001
0010
0011
0100
...

Which is exactly how you would count in binary. Therefore, you can use a single counter variable and then extract each flag value:

auto a = flagA, b = flagB, c = flagC, d = flagD;
for(auto i = 0; i < 16;   i) {
    flagA = (i>>3)&1;
    flagB = (i>>2)&1;
    flagC = (i>>1)&1;
    flagD = (i>>0)&1; // or just i&1
    myFunction();
}

CodePudding user response:

Just store the values the flags are supposed to have and reset the flags after each function call. You can do this via a template to avoid having to nest loops further and further when introducing additional flags:

/**
 * Sets the flags to the current combination and modifies \p values
 * to contain the next set of values returning true, if \p values
 * was reset to all false.
 *
 * \return true, if this is the last iteration 
 */
template<class...Flags>
bool SetFlags(std::array<bool, sizeof...(Flags)>& values, Flags&...flags)
{
    auto readPos = values.begin();
    ((flags = *readPos,   readPos), ...);

    // go to next flag combination
    //
    // effectively increment the binary number where
    //  values[0] is the value of the least significant bit and
    //  values[values.size() - 1] is the value of the most significant bit
    bool carry = true;
    auto writePos = values.begin();
    while (carry && writePos != values.end())
    {
        if (*writePos)
        {
            // set to 0 and keep carry
            *writePos = false;
        }
        else
        {
            *writePos = true;
            carry = false;
        }
          writePos;
    }
    return carry; // if the carry is still there, we went back to all false
}

template<class Funct, class ...Flags>
void RunForCombinations(Funct& f, Flags&...flags)
{
    if constexpr (sizeof...(Flags) == 0)
    {
        // run once, if no flags
        f();
    }
    else
    {
        std::array<bool, sizeof...(Flags)> values{}; // initially all false
        bool finished;
        do
        {
            finished = SetFlags(values, flags...);
            f();
        } while (!finished);
    }
    
}

struct Test
{

    std::random_device m_randomDevice;
    std::default_random_engine m_randomEngine;
    std::uniform_int_distribution<int> m_distribution{ 0, 1 };

    bool m_flag1;
    bool m_flag2;
    bool m_flag3;

    Test()
        : m_randomEngine(m_randomDevice())
    {}

    void operator()()
    {
        std::cout << "before:" << m_flag1 << m_flag2 << m_flag3 << '\n';
        m_flag1 = m_distribution(m_randomEngine);
        m_flag2 = m_distribution(m_randomEngine);
        m_flag3 = m_distribution(m_randomEngine);
        std::cout << "after: " << m_flag1 << m_flag2 << m_flag3 << '\n';
    }

}; 

int main()
{
    Test test;
    RunForCombinations(test, test.m_flag1, test.m_flag2, test.m_flag3);
}

Note: Here the Test class is simply introduced to group the information together instead of introducing globals. You could make the member variables globals, introduce a namespace scoped function test and use the same logic with test.m_flag1, ... replaced with the names of the global variables.

CodePudding user response:

You can use a union of bits:-

#include <iostream>

union Flags {
    struct {
        unsigned flagA:1;
        unsigned flagB:1;
        unsigned flagC:1;
        unsigned flagD:1;
    };
    unsigned c;
};

int main() {
    for(Flags flags{0}; flags.c<=0b1111;   flags.c){
        endl(std::cout << flags.flagA << flags.flagB << flags.flagC << flags.flagD);
    }
}
  • Related