-2

I am trying to run Google test with the below code. I am reading some memory location for register value with code similar to the examples below.

Header file :

typedef union MYREG
{
  uint32_t u32reg;
  uint8_t  au8byte[4];
} MYREG_t;

#define SET_VALUE       (0x00000002)
#define TEST_REGISTER   ((volatile MYREG_t*)0x2025111BUL)

In code I am reading and writing values as

void testcode()
{
  TEST_REGISTER->u32reg |= SET_VALUE;
  call_another_funct();
}

When i try to run google test by writing a test case for this function

TEST_F(sample_test, check)
{
  testcode();
}

I am getting below SEH error

First-chance exception at 0x0036B28F in test.exe: 0xC0000005: Access violation reading location 0x2025111B.
Unknown file: error: SEH exception with code 0xC0000005 thrown in the test body.

What is going wrong here ? Any suggestion would helpful for me to understand the error.

timrau
  • 22,578
  • 4
  • 51
  • 64
user2986042
  • 1,098
  • 2
  • 16
  • 37
  • 1
    Why do you think it is OK to read memory at address `0x2025111BUL`? – chux - Reinstate Monica Jan 30 '23 at 18:02
  • Google test framework is C++, the tag C is wrong. – 273K Jan 30 '23 at 18:02
  • What's this magical address `0x2025111BUL`? And why is it an unaligned value? – Weather Vane Jan 30 '23 at 18:03
  • 2
    You seem to assume `TEST_REGISTER->u32reg` would access a valid address. Address `0x2025111BUL` might be valid on your target hardware, but the chances are close to zero that you may access it in Google Test. – Gerhardh Jan 30 '23 at 18:03
  • @Gerhardh how can I make it make ? should I create object for the union? – user2986042 Jan 30 '23 at 18:04
  • 1
    You can't do isolated testing of code that interacts directly with hardware. You can only do integrated (code+hardware together) testing. Same holds true any time you have close coupling... only integrated testing is possible. That's one of the reasons for having an abstraction layer. (But eventually the hardware access beneath the abstraction, no matter how thin it is, still needs to be tested. And that needs to be done on the platform.) Sometimes there's an emulator which simulates the hardware with high enough fidelity. – Ben Voigt Jan 30 '23 at 18:05
  • inject the write to the register as an interface to your code under test. In your test environment you can then inject a simulated register (that you can test has the correct values, and/or set expected values from your test). Then in real code inject an implementation that actually writes to this (volatile) address. This pattern is called dependency injection and is key to writing unit testable code. (instead of a an interface you can also inject a lambda with either simulated or real register writes) – Pepijn Kramer Jan 30 '23 at 18:10
  • I am very confused why you would think that `0x2025111BUL` is a valid address. Do you have some special hardware perhaps a PCIe card with a device driver that says this is a valid address? This will not be valid on any normal PC without some hardware device and a device driver. – drescherjm Jan 30 '23 at 18:18
  • Or did you allocate some memory at this address in some way? – drescherjm Jan 30 '23 at 18:26

1 Answers1

0

Example of dependency injection for unit testability :

#include <memory>
#include <utility>

//-----------------------------------------------------------------------------
// define an interface (abstract base class) to communicate with your register
class register_itf
{
public:
    virtual ~register_itf() = default;

    virtual void write(const std::uint32_t value) = 0;
    virtual std::uint32_t read() const noexcept = 0;

protected:
    register_itf() = default;
};

//-----------------------------------------------------------------------------
// toy example of real register

class hardware_register final :
    public register_itf
{
public:
    explicit hardware_register(std::uint32_t* address) :
        m_address(address)
    {
    }
    ~hardware_register() override = default;

    void write(const std::uint32_t value) override
    {
        *m_address = value;
    }

    std::uint32_t read() const noexcept override
    {
        return *m_address;
    }

private:
    volatile std::uint32_t* m_address;
};

//-----------------------------------------------------------------------------
// you can also make this a google mock to test if it has been called etc...

class simulated_register final :
    public register_itf
{
public:
    simulated_register() = default;
    ~simulated_register() override = default;

    void write(const std::uint32_t value) override
    {
        m_value = value;
    }

    std::uint32_t read() const noexcept override
    {
        return m_value;
    }
    
private:
    std::uint32_t m_value;
};

//-----------------------------------------------------------------------------
// a device with a register, use dependency injection
// to inject a real or a simulated register

class device_t
{
public:
    explicit device_t(std::unique_ptr<register_itf>&& reg) :
        m_register(std::move(reg))
    {
    }

    void reset()
    {
        m_register->write(0x01);
    }

private:
    std::unique_ptr<register_itf> m_register;
};


int main()
{
    // production code on real hardware
    //device_t device{ std::make_unique<hardware_register>(0x2025111BUL) };
    
    // unit test code
    device_t device{ std::make_unique<simulated_register>() };

    // functional code using the register will be the same on production/test
    device.reset();

    return 1;

}
Pepijn Kramer
  • 9,356
  • 2
  • 8
  • 19