3

new keyword in C++ will throw an exception if insufficient memory but below code trying to return "NO_MEMORY" when new failed. This is bad because it will raise std::bad_alloc exception .

I am writing a unit test(gtest). How to create a scenario to catch this problem.

class base{


    public: base(){
        std::cout<<"base\n";
    }
};
 

std::string getInstance(base** obj){

    base *bObject = new base();
    *obj = bObject; //updated
     return (NULL == bObject) ? "NO_MEMORY" : "SUCCESS"; // here is problem if new fail it raise an exception. How to write unit test to catch this?
}

int main()
{
 
 base *base_obj;
 getInstance(&base_obj);
}
 
sonika
  • 41
  • 4
  • Unrelated, was it really your intent to not even use `obj` in that function ? Related to your question, an exception handler that returns something *other* than "SUCCESS" would seem inviting. – WhozCraig Jan 31 '22 at 12:05
  • My bad. I have updated my code – sonika Jan 31 '22 at 12:09
  • There's a non-trivial chance that `std::string::string(const char*` will throw when you're out of memory. Of course, if `base` fails because it tried to allocate a gigabyte, then the chance of `std::string{"NO_MEMORY"}` failing is a lot lower. – MSalters Jan 31 '22 at 13:36

2 Answers2

1

Have you looked into EXPECT_THROW?

If you cannot absolutely change your code, (which is required if you want to use gmock), you can globally overload the new operator as the other answer suggested.

However, you should do this carefully since this operator is used by other functions including the ones in google test.

One way to do this is to use a global variable that makes the new operator throw conditionally. Note that this is not the safest way, specially if your program is using multithreading

Below is one way of testing the scenario you described using this method and the global variable g_testing.

// https://stackoverflow.com/questions/70925635/gtest-on-new-keyword

#include "gtest/gtest.h"

// Global variable that indicates we are testing. In this case the global new
// operator throws.
bool g_testing = false;

// Overloading Global new operator
void *operator new(size_t sz) {
  void *m = malloc(sz);
  if (g_testing) {
    throw std::bad_alloc();
  }

  return m;
}

class base {
 public:
  base() { std::cout << "base\n"; }
};

std::string getInstance(base **obj) {
  base *bObject = new base();
  *obj = bObject;  // updated
  return (NULL == bObject)
             ? "NO_MEMORY"
             : "SUCCESS";  // here is problem if new fail it raise an exception.
  // How to write unit test to catch this?
}

TEST(Test_New, Failure) {
  base *base_obj;

  // Simple usage of EXPECT_THROW. This one should THROW.
  g_testing = true;
  EXPECT_THROW(getInstance(&base_obj), std::bad_alloc);
  g_testing = false;

  std::string result1;
  // You can put a block of code in it:
  g_testing = true;
  EXPECT_THROW({ result1 = getInstance(&base_obj); }, std::bad_alloc);
  g_testing = false;
  EXPECT_NE(result1, "SUCCESS");
}

TEST(Test_New, Success) {
  base *base_obj;

  std::string result2;
  // This one should NOT throw an exception.
  EXPECT_NO_THROW({ result2 = getInstance(&base_obj); });
  EXPECT_EQ(result2, "SUCCESS");
}

And here is your working example: https://godbolt.org/z/xffEoW9Kd

Ari
  • 7,251
  • 11
  • 40
  • 70
  • thanks Ari . But I can't modify the original code. I can't make any changes in the base() class and getInstance() function. Can we Allocate a high amount of memory in the gtest framework itself ? If possible can you please make the changes – sonika Jan 31 '22 at 15:50
  • I revised my answer! – Ari Jan 31 '22 at 18:05
0

First I think you need to catch the exception otherwise your program will never reach the point of returning NO_MEMORY:

std::string getInstance(base **obj) {
    try {
        if (!obj)
            throw std::invalid_argument("");

        *obj = new base();
        return "SUCCESS";
    }
    catch (const std::bad_alloc& e) {
        return "NO_MEMORY";
    }
    catch (...) {
        return "UNDEFINED_ERROR";
    }
}

A quick and dirty way for testing this would be to make the constructor (or an overloaded new) to throw std::bad_alloc:

#ifdef UNIT_TESTING
// simulate there is no memory
base::base() { throw std::bad_alloc; }
#else
base::base() { }
#endif

But I guess the proper way would be to use something like mockcpp

Edit: Since your are using gtest you might prefer using Google Mock to mock base with a constructor throwing bad_alloc instead of the dirty substitution of base::base

Odysseus
  • 1,213
  • 4
  • 12
  • You don't even need something like `mock` to simulate an allocation failure. `operator new` can be a user-provided function. Note that this is a **replacement**, not an overload. See https://stackoverflow.com/questions/583003/overloading-new-delete why overloading won't help you – MSalters Jan 31 '22 at 13:39
  • @MSalters The question is if he really needs simulate allocation error by cluttering the heap e.g. or just wants to test that his function behaves as intended when `std:bad_alloc` is thrown while object instantiation. Concerning `new` I don't get your point - why wouldn't `void * operator (size_t size) { throw std::bad_alloc; }` within `base` also simulate the failed instantiation? – Odysseus Jan 31 '22 at 14:12
  • I suppose you mean `operator new`? Because that's supposed to return `nullptr` in case of a failure. `std::bad_alloc` is generated by the `new` expression, whihc is one level up. – MSalters Jan 31 '22 at 14:25
  • Yes my fault, forgot the `new` - so wouldn't `void * base::operator new (size_t size) { throw std::bad_alloc; }` hide the standard `new`? – Odysseus Jan 31 '22 at 14:33
  • You may want to read up on operator new, new expressions and the new keyword. That's three different but related forms of "new", which of course work together. The short answer is that the class `operator new` must handle failure the same, i.e. return `nullptr`. In short, this is because these are primitives of the language, and exception handling is in comparison rather complex. It lives "at a higher level", so to say. – MSalters Jan 31 '22 at 14:52