samedi 8 avril 2023

Catching an abort signal

I am working on one of my assignments (recursion) and testing. I've run into a problem when trying to test a specific function. The function is called Ramanujan() https://math.stackexchange.com/questions/2381181/ramanujans-infinite-root. I was reading online on ways to test functions and found some very useful tricks (such as the google test library) but after trying to implement it I found out that it goes way above my level of knowledge. I settled for the old-fashioned "math by hand" testing, where I compare the output of the function with my calculations.

That worked for the 4 functions but I got stuck at the 5th one. I decide to use cassert and assert that the output of the ramanujan() function would always be less or equal to 4, because the series converges to 4. ( The constant is set to 3 because of testing).

While doing that I read that assert raised a SIGABRT signal upon failure, because my professors submission script won't accept that for some reason I had to come up with a signla_handler.

The signal handler works, but this gets printed to the console

prog: main.cpp:413: bool runTest5(): Assertion `output <= OUT' failed.

Is there a way to print out TEST5 FAILED instead of this?

Here is my code:

/**
 * @author Jakob Balkovec
 * @file main.cpp [Driver code]
 * @brief This assignment focuses on using recursion to solve various
 *        problems are given in the description [pdf]
 * @name Assignment 2
 */

#include <iostream> //std::cin, std::cout
#include <stdexcept> //try catch throw blocks
#include <string> // std::string lib for decToBase2()
#include <limits> //limts for int overflow
#include <cmath> //std::sqrt()
#include <unistd.h> //usleep
#include <csignal> //signal handler
#include <cassert> //assert library

#pragma message ("Compiling main.cpp")
#pragma message ("Last modified on 4/8/2023")

int const CONVERGE = 18; //ramanujan const for the series
long int const PAUSE = 1000000;
/**
 * @invariant The user will always provide an integer upon calling any of the 5 functions
 */

/**
 * @note typedefing it for better readbility
 */
typedef std::numeric_limits<int32_t> limits;

/**
 * @brief Upper and lower limits for 32bit (4Byte) intiger
 */
const int32_t MAX = limits::max();


/** @name printMenu()
 * @brief The function prints a menu for the user
 * @remark Handles UI
 * @return void-type
 */
void printMenu() {
  std::cout << "\n\nWelcome to the recursion assigment. What would you like to test?\n"
            << "[1] mysterySequence\n"
            << "[2] tennisBalls\n"
            << "[3] decToBase2\n"
            << "[4] isDivisibleBy7\n"
            << "[5] ramanjuan\n"
            << "[6] Run tests\n"
            << "[7] Exit\n\n";
  return;
}

/** @name goodbye()
 * @brief The function prompts the user goodbye
 * @remark Handles UI
 * @return void-type
 */
void goodbye() {
  std::cout << "Goodbye!\n\n";
}

/** @name getInput()
 * @brief The function prompts the user to enter a number corresponding to the menu
 * @return int choice (0 < choice < 8)
 */
int getInput() {
  int choice = 0;
  std::cout << "\n[Enter]: ";
  
  while (true) {
    try {
      std::cin >> choice;
      if (std::cin.fail()) { //std::cin.fail() if the input is not an intiger returns true
        /// @link https://cplusplus.com/forum/beginner/2957/
        
        std::cin.clear(); // clear error flags
        std::cin.ignore(10000, '\n'); // ignore up to 10000 characters or until a newline is encountered
        throw std::invalid_argument("[Invalid input]");
      }
      else if (choice < 1 || choice > 7) {
        throw std::out_of_range("Input out of range. Please enter an integer between 1 and 7.");
      }
      else {
        return choice;
      }
    }
    catch (const std::exception& error) {
      std::cout << error.what() << std::endl;
      std::cout << "[Re-enter]: ";
    }
  }
}

/** @name mysterySequence()
 * @brief Calculates the n-th term of the mystery sequence.
 * The sequence is defined recursively as follows:
 * 
 * @note Computed using a simple math formula
 * a[0] = 2
 * a[n] = a[n-1] * 3n for n >= 1
 * @note Computed using a simple math formula
 * 
 * @param n The term number to calculate.
 * @return The value of the n-th term of the sequence.
 */
int mysterySequence(int n) {
  if (n == 0) { /// @brief base case: the first term of the sequence is 2
    return 2; //a[1]
  } else if (n == 1) {
    return 3;
  } else {
    return mysterySequence(n-1) * mysterySequence(n-2); /// @brief recursive call to get the next term
  }
}

/** @name mysterySequenceFunc()
 * @brief Prompts the user for an input n and calls the mysterySequence function 
 * with n as the argument. 
 * @throw overflow_error
 * @return void-type
 */
void mysterySequenceFunc() {
  int n = 0;
  std::cout << "\n[Enter an index]: ";
  std::cin >> n;
  
  try {
    if (n < 0) {
      throw std::out_of_range("\nIndex should be positive\n");
    } else if (n > 46340) {
      throw std::overflow_error("\nInteger Overflow is bound to happen\n");
    } else {
      int index = mysterySequence(n);
      std::cout << "\nThe [" << n << "th] link in the sequence is " << index << "\n\n";
    }
  } catch (const std::exception& error) {
    std::cout << error.what() << std::endl;
  }
}

/** @name tennisBalls()
 * @brief Calculates the total number of tennis balls in a pyramid of tennis ball cans.
 * @param n The number of levels
 * @return The total number of tennis balls in the pyramid.
 */
int tennisBalls(int n) {
  if(n == 0) { /// @brief base case
    return 0;
  } else { /// @brief recursive case
    int balls = n*n;
    return balls + tennisBalls(n-1);
  }
}

/** @name tennisBallsFunc()
 * @brief Prompts the user to enter the number of cans in the base of a pyramid and 
 *        calculates the total
 *        number of tennis balls in the pyramid.
 * @throw std::overflow_error If n is greater than 46340, which can cause an integer overflow.
 * @return void-type
 */
void tennisBallsFunc() {
  int n = 0;
  std::cout << "\n[Enter the height of the pyramid]: ";
  std::cin >> n;
  
  try {
    if (n > 46340) {
      throw std::overflow_error("\nIntiger Overflow is bound to happen\n");
    } else if(n < 0) {
      throw std::out_of_range("\nHeight should be positive\n");
    }else {
      int result = tennisBalls(n);
      std::cout << "\nA pyramid with " << n << " levels holds " << result << " balls\n\n";
    }
  } catch (const std::exception& error) {
    std::cout << error.what() << std::endl;
  }
}

/** @name decToBase2()
 * @brief Converts an integer to its binary representation as a string. 
 * @param n The integer to convert to binary.
 * @return The binary representation of n as a string.
 */
std::string decToBase2(int n) {
  if (n == 0) { /// @brief base case
    return "0";
  } else if (n == 1) {
    return "1";
  } else { /// @brief recursive case
    int remainder = n % 2;
    return decToBase2(n / 2) + std::to_string(remainder);
    //std::to_string() returns a string with the representation of a value
    /// @link https://cplusplus.com/reference/string/to_string/
  }
}

/** @name decToBase2Func()
 * @brief Converts an integer to its binary representation.
 * @details Uses the decToBase2() function to convert the integer to binary, and prints the result.
 * @throws std::overflow_error If the integer is outside the range of a signed 32-bit integer.
 * @return void-type
 */
void decToBase2Func() {
  int n = 0;
  std::cout << "\n[Enter an integer]: ";
  std::cin >> n;
  
  try {
    if(n > MAX) { //int is defined as a 32 bit int (pre installed compiler on cs1)
      throw std::overflow_error("\nIntiger Overflow\n");
    } else if( n < 0) {
      throw std::out_of_range("\nThe intiger should be positive\n");   
    }else {
      std::cout << "\nThe integer " << n << " as binary: [" << decToBase2(n) << "]\n\n";
    }
  } catch(const std::exception &error) {
    std::cout << error.what() << std::endl;
  }
}

/** @name isDivisibleBy7()
 * @brief Check if an integer is divisible by 7
 * @param n The integer to check
 * @return true if n is divisible by 7, false otherwise
 */
bool isDivisibleBy7(int n) {
  if (n < 0) {
    n = abs(-n); // make n positive
  }
  
  if (n == 0 || n == 7) { //base case
    return true; // base case
    
  } else if (n < 10) {
    return false; // base case
    
  } else { //recursive case
    int lastDigit = n % 10;
    int remaining = n / 10;
    int doubled = lastDigit * 2;
    int newNumber = remaining - doubled;
    return isDivisibleBy7(newNumber); // recursive case
  }
}

/** @name isDivisibleBy7Func()
 * @brief This function takes an integer input from the user and checks if it is divisible by 7
 * calling the isDivisibleBy7() function.
 * @throw overflow_error error.
 * @return void-type
 */
void isDivisibleBy7Func() {
  int n = 0;
  std::cout << "\n[Enter an integer]: ";
  std::cin >> n;
  
  try{
    if(n > MAX) {
      throw std::overflow_error("\nInteger Overflow\n");
    } else if( n < 0) {
      throw std::out_of_range("\nThe intiger should be positive\n");   
    } else{
      bool isDivisible = isDivisibleBy7(n);
      if(isDivisible) {
        std::cout << "\nThe integer " << n << " is divisible by 7\n\n";
        }else {
        std::cout << "\nThe integer " << n << " is not divisible by 7\n\n";
      }
    }
  } catch(const std::exception &error) {
    std::cout << error.what() << std::endl;
  }
}

/** @name ramanujan()
 * @brief Calculates the value of the Ramanujan series to a specified depth using recursion.
 * @param depth The depth of the Ramanujan series.
 * @param current The current depth of the recursion. Defaults to 0.
 * @return The value of the Ramanujan series at the specified depth.
*/
double ramanujan(int depth, int current = 0){ //current passed as default argument
  if(current > depth) {// base case
    return 0;
  }else { //recursive case
    return (current + 1) * sqrt(6 + current + ramanujan(depth, current + 1));
  }
}

/** @name ramanujanFunc()
 * @brief Computes the value of the Ramanujan series with the given depth using recursion.
 * @param depth The depth of the Ramanujan series to compute.
 * @return The value of the Ramanujan series with the given depth.
 * @throw std::overflow_error If the depth is too large to compute.
 * @throw std::out_of_range If the depth is negative.
 */
void ramanujanFunc() {
  int n = 0;
  std::cout << "\n[Enter the depth of the series]: ";
  std::cin >> n;
  
  try {
    if(n > MAX) {
      throw std::overflow_error("\nInteger Overflow\n");
    } else if(n < 0) {
      throw std::logic_error("\nThe depth should be positive\n"); //if n < 0 we need complex nums
    }else {
      std::cout << "\nResult at depth [" << n << "] is " << ramanujan(n) << "\n";
      std::cout << "\nResult at infinte depth is " << ramanujan(100) << "\n"; //int overflow
      std::cout << "\n";
    }
  }catch (const std::exception &error) {
    std::cout << error.what() << std::endl;
  }
}

/** @name runTest1()
 * @brief Runs the test for mysterySequence() using comparison with expected output and the actual output
 * @return booleans wheter the output mathes (false || true)
 */
bool runTest1() {

  int expectedOutput[] = {2, 3, 6, 18, 108, 1944, 209952, 408146688};

  int input[] = {0, 1, 2, 3, 4, 5, 6, 7};
  int numTests = sizeof(expectedOutput) / sizeof(expectedOutput[0]);

  for (auto i = 0; i < numTests; i++) {
    if (mysterySequence(input[i]) != expectedOutput[i]) {
      std::cout << "*** For " << input[i] << " expected " << expectedOutput[i] << " but got " << mysterySequence(input[i]) << std::endl << std::endl;
      return false;
    }
  }
  return true;
}

/** @name runTest1()
 * @brief Runs the test for tennisBalls() using comparison with expected output and the actual output
 * @return booleans wheter the output mathes (false || true)
 */
bool runTest2(){
  int expectedOutput[] = {0, 1, 14, 385, 650, 5525};

  int input[] = {0, 1, 3, 10, 12, 25};
  int numTests = sizeof(expectedOutput) / sizeof(expectedOutput[0]);

  for(auto i = 0; i < numTests; i++) {
    if(tennisBalls(input[i]) != expectedOutput[i]) {
      std::cout << "*** For " << input[i] << " expected " << expectedOutput[i] << " but got " << tennisBalls(input[i]) << std::endl << std::endl;
      return false; //test failed
    }
  }
  return true;
}

/** @name runTest1()
 * @brief Runs the test for decToBase2() using comparison with expected output and the actual output
 * @return booleans wheter the output mathes (false || true)
 */
bool runTest3(){
  typedef std::string string;
  string expectedOutput[] = {"0", "1", "1101", "100000", "101000001", "10000010000100"};

  int input[] = {0, 1, 13, 32, 321, 8324};
  int numTests = sizeof(expectedOutput) / sizeof(expectedOutput[0]);

  for(auto i = 0; i < numTests; i++) {
    if(decToBase2(input[i]) != expectedOutput[i]) {
      std::cout << "*** For " << input[i] << " expected " << expectedOutput[i] << " but got " << decToBase2(input[i]) << std::endl << std::endl;
      return false; //test failed
    }
  }
  return true;
}

/** @name runTest1()
 * @brief Runs the test for isDivisbleBy7() using comparison with expected output and the actual output
 * @return booleans wheter the output mathes (false || true)
 */
bool runTest4(){
  bool expectedOutput[] = {false, true, false, false, true, true, false};

  int input[] = {1, 7, 31, 1073, 1729, 5838, 151932};
  int numTests = sizeof(expectedOutput) / sizeof(expectedOutput[0]);

  for(auto i = 0; i < numTests; i++) {
    if(isDivisibleBy7(input[i] != expectedOutput[i])) {
      std::cout << "*** For " << input[i] << " expected " << expectedOutput[i] << " but got " << isDivisibleBy7(input[i]) << std::endl << std::endl;
      return false; //test failed
    }
  }
  return true;
}

/** @name runTest1()
 * @brief Runs the test for ramanujan() using assertion 
 * (asserts the output is always less than or equal to 4)
 * @return useless boolean...assert calls abort() upon failure
 */
bool runTest5() {
  int const OUT = 3; //const for testing

  int input[] {1, 3, 7, 23, 10, 34, 55, 12};
  float output; //declare var for output
  bool test = true;

  int numTests = sizeof(input) / sizeof(input[0]);
  for(auto i = 0; i < numTests; i++) {
    output = ramanujan(input[i]);
    assert(output <= OUT);
  }
  //create a singal handler for assert
  return test;
}

void signal_handler(int signal) {
    if (signal == SIGABRT) {
        std::exit(EXIT_FAILURE); //std::exit(signal)
    }
}
 
void runTestsFunc() {
  #define FAIL false
  #define PASS true
  
  bool test1 = runTest1();
  if(test1 == PASS) {
    std::cout << "\n*** TEST1 PASSED\n";
    usleep(PAUSE);
  }else if(test1 == FAIL) {
    std::cout << "\n*** TEST1 FAILED\n";
    usleep(PAUSE);
  }

  bool test2 = runTest2();
  if(test2 == PASS) {
    std::cout << "*** TEST2 PASSED\n";
    usleep(PAUSE);
  }else if(test2 == FAIL) {
    std::cout << "*** TEST2 FAILED\n";
    usleep(PAUSE);
  }

  bool test3 = runTest3();
  if(test3 == PASS) {
    std::cout << "*** TEST3 PASSED\n";
    usleep(PAUSE);
  }else if(test3 == FAIL) {
    std::cout << "*** TEST3 FAILED\n";
    usleep(PAUSE);
  }

  bool test4 = runTest4();
  if(test4 == PASS) {
    std::cout << "*** TEST4 PASSED\n";
    usleep(PAUSE);
  }else if(test4 == FAIL) {
    std::cout << "*** TEST4 FAILED\n";
    usleep(PAUSE);
  }

  std::signal(SIGABRT, signal_handler);
  bool test5 = runTest5();

  if (test5 == PASS) {
      std::cout << "*** TEST5 PASSED\n";
      usleep(PAUSE);
  } else {
      std::cout << "*** TEST5 FAILED\n";
      usleep(PAUSE);
  }
    

bool allPassed = test1 && test2 && test3 && test4 && test5;

try {
    if (allPassed) {
        std::cout << "*** ALL TEST CASES PASSED\n";
    } else {
        // throw an exception if any test case failed
        throw std::runtime_error("FAIL -> COMPILATION TERMINATED");
    }
} catch (const std::exception& error) {
    std::cout << "*** ERROR: " << error.what() << "\n";
    exit(EXIT_FAILURE);
}
}

int main () {
  while(1) { //change back to while true
    printMenu();
    switch(getInput()) {
    case 1: {
      mysterySequenceFunc();
      break;
    }
    case 2: {
      tennisBallsFunc();
      break;
    }
    case 3: {
      decToBase2Func();
      break;
    }
    case 4: {
      isDivisibleBy7Func();
      break;
    }
    case 5: {
      ramanujanFunc();
      break;
    }
    case 6: {
      runTestsFunc();
      break;
    }
    case 7: {
      goodbye();
      exit(EXIT_FAILURE);
      break;
    }
    default:  {
      /**
       * @brief add more or edit
       */
      std::cout << "In default";
      break;
    }
    }
  }
}

I would be very grateful for any response I could get and any style and formatting tips. My professor requires a quite "harsh and heavy" function decomposition that's why my main is so short. I am also not allowed to work with classes and multiple files for this assignment.

Thank you!

Aucun commentaire:

Enregistrer un commentaire