diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..c3047a4 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,65 @@ +# Use a C++11 distro +dist: trusty +sudo: required + +# Enable C++ support +language: cpp + +# Compiler selection +compiler: gcc + +env: + global: + # coverity key + - secure: "NKZbBnMALGIIQJy/s2kc3EST/stw+gjhtrGq0jkbsWr7Wx3FH+lmLeHNsDXRnD1VbpG02c5YsLllqz9OVu+0yxWGepvKNmCz1cNITIALEHbrax8/Af9LzPRL/QZxS/Qe11sMuySp4X16mFBUyxMd/X+I9i96Xf1vKkZABklYD1Q=" + +# addons +addons: + apt: + packages: + - gcc-6 + - g++-6 + sources: + - ubuntu-toolchain-r-test + + coverity_scan: + project: + name: "mpaland/printf" + description: "Tiny printf implementation" + notification_email: marco@paland.com + build_command_prepend: "make clean" + build_command: "make" + branch_pattern: master + +before_install: + # install coveralls + - pip install --user cpp-coveralls + # connect coverity + - echo -n | openssl s_client -connect scan.coverity.com:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' | sudo tee -a /etc/ssl/certs/ca- + +# Active branches +branches: + only: + - master + +script: + # Link gcc-6 and g++-6 to their standard commands + - sudo rm /usr/bin/gcc + - sudo rm /usr/bin/g++ + - sudo ln -s /usr/bin/gcc-6 /usr/bin/gcc + - sudo ln -s /usr/bin/g++-6 /usr/bin/g++ + # Export CC and CXX + - export CC=/usr/bin/gcc-6 + - export CXX=/usr/bin/g++-6 + # Check versions of gcc, g++ + - gcc -v && g++ -v + # Run build commands + - make + # execute the text suite + - bin/test_suite -d yes + # coverall profiling + - tmp/cov/test_suite + +after_success: + # Report to coveralls + - coveralls --build-root ${TRAVIS_BUILD_DIR} --include printf.cpp --gcov 'gcov-6' --gcov-options '\-lp' diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..7b28a1a --- /dev/null +++ b/Makefile @@ -0,0 +1,271 @@ +# ------------------------------------------------------------------------------ +# +# Generic Makefile +# +# Copyright Marco Paland 2007 - 2017 +# Distributed under the MIT License +# +# ------------------------------------------------------------------------------ + +# ------------------------------------------------------------------------------ +# Paths +# ------------------------------------------------------------------------------ +PATH_TOOLS_CC = /usr/bin/ +PATH_TOOLS_CC_LIB = /usr/lib/ +PATH_TOOLS_UTIL = + +PATH_BIN = bin +PATH_TMP = tmp +PATH_NUL = /dev/null +PATH_OBJ = $(PATH_TMP)/obj +PATH_LST = $(PATH_TMP)/lst +PATH_ERR = $(PATH_TMP)/err +PATH_PRE = $(PATH_TMP)/pre +PATH_COV = $(PATH_TMP)/cov + + +# ------------------------------------------------------------------------------ +# Application to build +# ------------------------------------------------------------------------------ + +APP = test_suite + + +# ----------------------------------------------------------------------------- +# Project file list +# Format is: +# FILES_PRJ = file1 \ +# foo/file2 \ +# bar/file3 +# ----------------------------------------------------------------------------- + +FILES_PRJ = test/test_suite + + +# ------------------------------------------------------------------------------ +# Additional include files and compiler defines +# Format is: +# C_INCLUDES = -Iinclude_path1 \ +# -Iinclude_path2 \ +# -Iinclude_path3 \ +# ------------------------------------------------------------------------------ + +C_INCLUDES = + +C_DEFINES = + + +# ------------------------------------------------------------------------------ +# The target name and location +# ------------------------------------------------------------------------------ +TRG = $(PATH_BIN)/$(APP) + + +# ------------------------------------------------------------------------------ +# object files +# ------------------------------------------------------------------------------ +FILES_TMP = $(FILES_PRJ) +FILES_O = $(addsuffix .o, $(FILES_TMP)) + + +# ------------------------------------------------------------------------------ +# VPATH definition +# +# VPATH is required for the maker to find the C-/ASM-Source files. +# Extract the directory/module names from the file list with the dir +# command and remove the duplicated directory names with the sort command. +# FILES_PRJ is listed first to make sure that the source files in the project +# directory are searched first. +# ------------------------------------------------------------------------------ +VPATH := $(sort $(dir $(FILES_TMP))) + + +# ------------------------------------------------------------------------------ +# Development tools +# ------------------------------------------------------------------------------ +AR = $(PATH_TOOLS_CC)ar +AS = $(PATH_TOOLS_CC)g++ +CC = $(PATH_TOOLS_CC)g++ +CL = $(PATH_TOOLS_CC)g++ +NM = $(PATH_TOOLS_CC)nm +GCOV = $(PATH_TOOLS_CC)gcov +OBJDUMP = $(PATH_TOOLS_CC)objdump +OBJCOPY = $(PATH_TOOLS_CC)objcopy +READELF = $(PATH_TOOLS_CC)readelf +SIZE = $(PATH_TOOLS_CC)size + +ECHO = $(PATH_TOOLS_UTIL)echo +MAKE = $(PATH_TOOLS_UTIL)make +MKDIR = $(PATH_TOOLS_UTIL)mkdir +RM = $(PATH_TOOLS_UTIL)rm +SED = $(PATH_TOOLS_UTIL)sed + + +# ------------------------------------------------------------------------------ +# Compiler flags for the target architecture +# ------------------------------------------------------------------------------ + +GCCFLAGS = $(C_INCLUDES) \ + $(C_DEFINES) \ + -std=c++11 \ + -g \ + -Wall \ + -pedantic \ + -Wmain \ + -Wundef \ + -Wsign-conversion \ + -Wuninitialized \ + -Wshadow \ + -Wunreachable-code \ + -Wswitch-default \ + -Wswitch \ + -Wcast-align \ + -Wmissing-include-dirs \ + -Winit-self \ + -Wdouble-promotion \ + -gdwarf-2 \ + -fno-exceptions \ + -O2 \ + -ffunction-sections \ + -ffat-lto-objects \ + -fdata-sections \ + -fverbose-asm \ + -Wextra \ + -Wunused-parameter \ + -Wfloat-equal + +CFLAGS = $(GCCFLAGS) \ + -Wunsuffixed-float-constants \ + -x c \ + -std=c99 + +CPPFLAGS = $(GCCFLAGS) \ + -x c++ \ + -fno-rtti \ + -fstrict-enums \ + -fno-use-cxa-atexit \ + -fno-use-cxa-get-exception-ptr \ + -fno-nonansi-builtins \ + -fno-threadsafe-statics \ + -fno-enforce-eh-specs \ + -ftemplate-depth-64 \ + -fexceptions + +AFLAGS = $(GCCFLAGS) \ + -x assembler + +LFLAGS = $(GCCFLAGS) \ + -x none \ + -Wl,--gc-sections + +# ------------------------------------------------------------------------------ +# Targets +# ------------------------------------------------------------------------------ + +# ------------------------------------------------------------------------------ +# Main-Dependencies (app: all) +# ------------------------------------------------------------------------------ +.PHONY: all +all: clean_prj $(TRG) $(TRG)_nm.txt + + +# ------------------------------------------------------------------------------ +# Main-Dependencies (app: rebuild) +# ------------------------------------------------------------------------------ +.PHONY: rebuild +rebuild: clean $(TRG) $(TRG)_nm.txt + + +# ------------------------------------------------------------------------------ +# clean project +# ------------------------------------------------------------------------------ +.PHONY: clean_prj +clean_prj: + @-$(ECHO) +++ cleaning project + @-$(RM) -rf $(PATH_BIN) 2> $(PATH_NUL) + @-$(MKDIR) -p $(PATH_BIN) + @-$(MKDIR) -p $(PATH_OBJ) + @-$(MKDIR) -p $(PATH_ERR) + @-$(MKDIR) -p $(PATH_LST) + @-$(MKDIR) -p $(PATH_PRE) + @-$(MKDIR) -p $(PATH_COV) + + +# ------------------------------------------------------------------------------ +# clean all +# ------------------------------------------------------------------------------ +.PHONY: clean +clean: + @-$(ECHO) +++ cleaning all + @-$(RM) -rf $(PATH_BIN) 2> $(PATH_NUL) + @-$(RM) -rf $(PATH_TMP) 2> $(PATH_NUL) + @-$(MKDIR) -p $(PATH_BIN) + @-$(MKDIR) -p $(PATH_OBJ) + @-$(MKDIR) -p $(PATH_ERR) + @-$(MKDIR) -p $(PATH_LST) + @-$(MKDIR) -p $(PATH_COV) + + +# ------------------------------------------------------------------------------ +# print the GNUmake version and the compiler version +# ------------------------------------------------------------------------------ +.PHONY: version +version: + # Print the GNU make version and the compiler version + @$(ECHO) GNUmake version: + @$(MAKE) --version + @$(ECHO) GCC version: + @$(CL) -v + + +# ------------------------------------------------------------------------------ +# Rules +# ------------------------------------------------------------------------------ + +# ------------------------------------------------------------------------------ +# Link/locate application +# ------------------------------------------------------------------------------ +$(TRG) : $(FILES_O) + @-$(ECHO) +++ linkink application to generate: $(TRG) + @-$(CL) $(LFLAGS) -L. -lc $(PATH_OBJ)/*.o -Wl,-Map,$(TRG).map -o $(TRG) + # profiling + @-$(CL) $(LFLAGS) -L. -lc $(PATH_COV)/*.o --coverage -o $(PATH_COV)/$(APP) + + +# ------------------------------------------------------------------------------ +# parse the object files to obtain symbol information, and create a size summary +# ------------------------------------------------------------------------------ +$(TRG)_nm.txt : $(TRG) + @-$(ECHO) +++ parsing symbols with nm to generate: $(TRG)_nm.txt + @-$(NM) --numeric-sort --print-size $(TRG) > $(TRG)_nm.txt + @-$(ECHO) +++ demangling symbols with c++filt to generate: $(TRG)_cppfilt.txt + @-$(NM) --numeric-sort --print-size $(TRG) | $(CPPFILT) > $(TRG)_cppfilt.txt + @-$(ECHO) +++ creating size summary table with size to generate: $(TRG)_size.txt + @-$(SIZE) -A -t $(TRG) > $(TRG)_size.txt + + +%.o : %.cpp + @$(ECHO) +++ compile: $< + # Compile the source file + # ...and Reformat (using sed) any possible error/warning messages for the VisualStudio(R) output window + # ...and Create an assembly listing using objdump + # ...and Generate a dependency file (using the -MM flag) + @-$(CL) $(CPPFLAGS) $< -E -o $(PATH_PRE)/$(basename $(@F)).pre + @-$(CL) $(CPPFLAGS) $< -c -o $(PATH_OBJ)/$(basename $(@F)).o 2> $(PATH_ERR)/$(basename $(@F)).err + @-$(SED) -e 's|.h:\([0-9]*\),|.h(\1) :|' -e 's|:\([0-9]*\):|(\1) :|' $(PATH_ERR)/$(basename $(@F)).err + @-$(OBJDUMP) --disassemble --line-numbers -S $(PATH_OBJ)/$(basename $(@F)).o > $(PATH_LST)/$(basename $(@F)).lst + @-$(CL) $(CPPFLAGS) $< -MM > $(PATH_OBJ)/$(basename $(@F)).d + # profiling + @-$(CL) $(CPPFLAGS) -O0 --coverage $< -c -o $(PATH_COV)/$(basename $(@F)).o 2> $(PATH_NUL) + +%.o : %.c + @$(ECHO) +++ compile: $< + # Compile the source file + # ...and Reformat (using sed) any possible error/warning messages for the VisualStudio(R) output window + # ...and Create an assembly listing using objdump + # ...and Generate a dependency file (using the -MM flag) + @-$(CL) $(CFLAGS) $< -E -o $(PATH_PRE)/$(basename $(@F)).pre + @-$(CC) $(CFLAGS) $< -c -o $(PATH_OBJ)/$(basename $(@F)).o 2> $(PATH_ERR)/$(basename $(@F)).err + @-$(SED) -e 's|.h:\([0-9]*\),|.h(\1) :|' -e 's|:\([0-9]*\):|(\1) :|' $(PATH_ERR)/$(basename $(@F)).err + @-$(OBJDUMP) -S $(PATH_OBJ)/$(basename $(@F)).o > $(PATH_LST)/$(basename $(@F)).lst + @-$(CC) $(CFLAGS) $< -MM > $(PATH_OBJ)/$(basename $(@F)).d diff --git a/README.md b/README.md index 8ec250f..f2e230f 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,34 @@ # printf / sprintf for embedded systems -This is a tiny but fully loaded printf, sprintf and snprintf implementation. +[![Build Status](https://travis-ci.org/mpaland/printf.svg?branch=master)](https://travis-ci.org/mpaland/printf) +[![Coveralls Status](https://coveralls.io/repos/github/mpaland/printf/badge.svg?branch=master)](https://coveralls.io/github/mpaland/printf?branch=master) +[![Coverity Status](https://img.shields.io/coverity/scan/14180.svg)](https://scan.coverity.com/projects/mpaland-printf) +[![Github Issues](https://img.shields.io/github/issues/mpaland/printf.svg)](http://github.com/mpaland/printf/issues) +[![Github Releases](https://img.shields.io/github/release/mpaland/printf.svg)](https://github.com/mpaland/printf/releases) +[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/mpaland/avl_array/master/LICENSE) + +This is a tiny but **fully loaded** printf, sprintf and snprintf implementation. Primarily designed for usage in embedded systems, where printf is not available due to memory issues or in avoidance of linking against libc. -Using the standard libc printf may pull a lot of unwanted library stuff and can bloat code size about 20k. In this case the following implementation can be used. -Absolutely **NO dependencies** are required, printf.cpp brings all necessary routines, even its own fast ftoa conversion. +Using the standard libc printf may pull **a lot** of unwanted library stuff and can bloat code size about 20k. In this case the following implementation can be used. +Absolutely **NO dependencies** are required, printf.cpp brings all necessary routines, even its own fast `ftoa` conversion. If memory footprint is really a critical issue, floating point support can be turned off via the `PRINTF_FLOAT_SUPPORT` compiler switch. When using printf (instead of sprintf) you have to provide your own `_putchar()` low level function as console output. -## Design goals +## Highligths and design goals There is a boatload of so called 'tiny' printf implementations around. So why this one? -I tested many implementations, but most of them have very limited flag/specifier support, a lot of other dependencies or are just not standard compliant and failing the test suite. +I've tested many implementations, but most of them have very limited flag/specifier support, a lot of other dependencies or are just not standard compliant and failing the test suite. Therefore I decided to write an own implementation which meets the following items: - Very small implementation (< 500 code lines) - NO dependencies, no libs, just one module file - Support of all important flags, width and precision sub-specifiers (see below) - - Support of float number representation (with an own fast ftoa) + - Support of dec/float number representation (with an own fast itoa/ftoa) - Reentrant and thread-safe, malloc free - - LINT and compiler L4 warning free, clean code - - Extensive test suite passing + - LINT and compiler L4 warning free, coverity clean, automotive ready + - Extensive test suite (> 260 test cases) passing - MIT license @@ -34,7 +41,8 @@ Usage is 1:1 like the according stdio.h library version: `int sprintf(char* buffer, const char* format, ...);` `int snprintf(char* buffer, size_t count, const char* format, ...);` -**Due to genaral security reasons it is highly recommended to use snprintf (with the max buffer size as `count` parameter) only.** +**Due to genaral security reasons it is highly recommended to use `snprintf` (with the max buffer size as `count` parameter) only.** +`sprintf` has no buffer limitation, so when necessary - use it with care! ## Format specifiers diff --git a/printf.cpp b/printf.cpp index 22831d0..d5f3603 100644 --- a/printf.cpp +++ b/printf.cpp @@ -65,18 +65,18 @@ // internal strlen, returns the length of the string static inline size_t _strlen(const char* str) { - size_t len = 0U; - while (str[len] != '\0') { - len++; - } - return len; -} + size_t len = 0U; + while (str[len] != '\0') { + len++; + } + return len; +} -// returns 1 if char is a digit, 0 if not -static inline unsigned int _is_digit(char ch) +// returns true if char is a digit +static inline bool _is_digit(char ch) { - return (ch >= '0' && ch <= '9') ? 1U : 0U; + return (ch >= '0') && (ch <= '9'); } @@ -85,7 +85,7 @@ static inline unsigned int _atoi(const char** str) { unsigned int i = 0U; while (_is_digit(**str)) { - i = i * 10U + *((*str)++) - '0'; + i = i * 10U + (unsigned int)(*((*str)++) - '0'); } return i; } @@ -113,7 +113,7 @@ static size_t _ntoa(T value, char* buffer, unsigned int base, size_t maxlen, uns // write if precision != 0 and value is != 0 if (!(flags & FLAGS_PRECISION) || (value != 0)) { do { - char digit = (char)((unsigned)value % base); + char digit = (char)(value % (T)base); buf[len++] = digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10; value /= (T)base; } while ((len < NTOA_BUFFER_SIZE) && (value > 0)); @@ -197,8 +197,8 @@ static size_t _ftoa(double value, char* buffer, size_t maxlen, unsigned int prec const double thres_max = (double)0x7FFFFFFF; char buf[FTOA_BUFFER_SIZE]; - size_t len = 0U; - double diff = 0.0; + size_t len = 0U; + double diff = 0; // powers of 10 static const double pow10[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 }; @@ -225,7 +225,7 @@ static size_t _ftoa(double value, char* buffer, size_t maxlen, unsigned int prec if (diff > 0.5) { ++frac; - // handle rollover, e.g. case 0.99 with prec 1 is 1.0 + // handle rollover, e.g. case 0.99 with prec 1 is 1.0 if (frac >= pow10[prec]) { frac = 0; ++whole; @@ -271,13 +271,13 @@ static size_t _ftoa(double value, char* buffer, size_t maxlen, unsigned int prec } } - // do whole part - // Take care of sign conversion. Number is reversed - size_t wlen = 0U; - do { + // do whole part, number is reversed + while (len < FTOA_BUFFER_SIZE) { buf[len++] = (char)(48 + (whole % 10)); - wlen++; - } while ((len < FTOA_BUFFER_SIZE) && (whole /= 10)); + if (!(whole /= 10)) { + break; + } + } // pad leading zeros while (!(flags & FLAGS_LEFT) && (len < prec) && (len < FTOA_BUFFER_SIZE)) { @@ -368,7 +368,7 @@ static size_t vsnprintf(char* buffer, size_t buffer_len, const char* format, va_ width = _atoi(&format); } else if (*format == '*') { - const int w = (unsigned int)va_arg(va, int); + const int w = va_arg(va, int); if (w < 0) { flags |= FLAGS_LEFT; // reverse padding width = (unsigned int)-w; @@ -410,8 +410,6 @@ static size_t vsnprintf(char* buffer, size_t buffer_len, const char* format, va_ case 'X' : case 'o' : case 'b' : - // no plus or space flag for the types above - flags &= ~(FLAGS_PLUS | FLAGS_SPACE); case 'd' : case 'i' : { // set the base @@ -435,8 +433,13 @@ static size_t vsnprintf(char* buffer, size_t buffer_len, const char* format, va_ flags |= FLAGS_UPPERCASE; } + // no plus or space flag for u, x, X, o, b + if ((*format != 'i') && (*format != 'd')) { + flags &= ~(FLAGS_PLUS | FLAGS_SPACE); + } + // convert the integer - if (*format == 'i' || *format == 'd') { + if ((*format == 'i') || (*format == 'd')) { // signed if (flags & FLAGS_LONG_LONG) { idx += _ntoa(va_arg(va, long long), &buffer[idx], base, buffer_len - idx, precision, width, flags); @@ -479,7 +482,7 @@ static size_t vsnprintf(char* buffer, size_t buffer_len, const char* format, va_ } } // char output - buffer[idx++] = va_arg(va, char); + buffer[idx++] = (char)va_arg(va, int); // post padding if (flags & FLAGS_LEFT) { while ((idx < buffer_len) && (l++ < width)) { @@ -537,6 +540,7 @@ static size_t vsnprintf(char* buffer, size_t buffer_len, const char* format, va_ default : buffer[idx++] = *format; + format++; break; } } diff --git a/test/test_suite.cpp b/test/test_suite.cpp index a90f701..0b2f680 100644 --- a/test/test_suite.cpp +++ b/test/test_suite.cpp @@ -203,6 +203,9 @@ TEST_CASE("0 flag", "[]" ) { test::sprintf(buffer, "%0d", 42); REQUIRE(!strcmp(buffer, "42")); + test::sprintf(buffer, "%0ld", 42L); + REQUIRE(!strcmp(buffer, "42")); + test::sprintf(buffer, "%0d", -42); REQUIRE(!strcmp(buffer, "-42")); @@ -864,4 +867,105 @@ TEST_CASE("float", "[]" ) { test::sprintf(buffer, "%+6.2f", 42.8952); REQUIRE(!strcmp(buffer, "+42.90")); + test::sprintf(buffer, "%+5.1f", 42.9252); + REQUIRE(!strcmp(buffer, "+42.9")); + + test::sprintf(buffer, "%f", 42.5); + REQUIRE(!strcmp(buffer, "42.500000")); + + test::sprintf(buffer, "%.1f", 42.5); + REQUIRE(!strcmp(buffer, "42.5")); + + test::sprintf(buffer, "%f", (float)42167); + REQUIRE(!strcmp(buffer, "42167.000000")); +} + + +TEST_CASE("types", "[]" ) { + char buffer[100]; + + test::sprintf(buffer, "%i", 1234); + REQUIRE(!strcmp(buffer, "1234")); + + test::sprintf(buffer, "%li", 30L); + REQUIRE(!strcmp(buffer, "30")); + + test::sprintf(buffer, "%lli", 30LL); + REQUIRE(!strcmp(buffer, "30")); + + test::sprintf(buffer, "%lu", 100000L); + REQUIRE(!strcmp(buffer, "100000")); + + test::sprintf(buffer, "%llu", 281474976710656LLU); + REQUIRE(!strcmp(buffer, "281474976710656")); + + test::sprintf(buffer, "%b", 60000); + REQUIRE(!strcmp(buffer, "1110101001100000")); + + test::sprintf(buffer, "%lb", 12345678L); + REQUIRE(!strcmp(buffer, "101111000110000101001110")); + + test::sprintf(buffer, "%o", 60000); + REQUIRE(!strcmp(buffer, "165140")); + + test::sprintf(buffer, "%lo", 12345678L); + REQUIRE(!strcmp(buffer, "57060516")); + + test::sprintf(buffer, "%lx", 0x12345678L); + REQUIRE(!strcmp(buffer, "12345678")); + + test::sprintf(buffer, "%llx", 0x1234567891234567LLU); + REQUIRE(!strcmp(buffer, "1234567891234567")); + + test::sprintf(buffer, "%lx", 0xabcdefabL); + REQUIRE(!strcmp(buffer, "abcdefab")); + + test::sprintf(buffer, "%lX", 0xabcdefabL); + REQUIRE(!strcmp(buffer, "ABCDEFAB")); + + test::sprintf(buffer, "%c", 'v'); + REQUIRE(!strcmp(buffer, "v")); + + test::sprintf(buffer, "%cv", 'w'); + REQUIRE(!strcmp(buffer, "wv")); + + test::sprintf(buffer, "%s", "A Test"); + REQUIRE(!strcmp(buffer, "A Test")); +} + + +TEST_CASE("pointer", "[]" ) { + char buffer[100]; + + test::sprintf(buffer, "%p", (void*)0x1234U); + if (sizeof(void*) == 4U) { + REQUIRE(!strcmp(buffer, "00001234")); + } + else { + REQUIRE(!strcmp(buffer, "0000000000001234")); + } + + test::sprintf(buffer, "%p", (void*)0x12345678U); + if (sizeof(void*) == 4U) { + REQUIRE(!strcmp(buffer, "12345678")); + } + else { + REQUIRE(!strcmp(buffer, "0000000012345678")); + } +} + + +TEST_CASE("unknown flag", "[]" ) { + char buffer[100]; + + test::sprintf(buffer, "%kmarco", 42, 37); + REQUIRE(!strcmp(buffer, "kmarco")); +} + + +TEST_CASE("misc", "[]" ) { + char buffer[100]; + + test::sprintf(buffer, "%u%u%ctest%d %s", 5, 3000, 'a', -20, "bit"); + REQUIRE(!strcmp(buffer, "53000atest-20 bit")); }