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