Skip to content

Commit

Permalink
Merge pull request #28 from filip-stenstrom/fix_locale_other_platforms
Browse files Browse the repository at this point in the history
Fix setlocale during parsing for other platforms
  • Loading branch information
filip-stenstrom authored Oct 6, 2020
2 parents 391be90 + 7fe8292 commit 341c2c9
Show file tree
Hide file tree
Showing 20 changed files with 358 additions and 93 deletions.
19 changes: 13 additions & 6 deletions Config.cmake/test_fmi1.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ target_link_libraries(fmi1_type_definitions_test ${FMILIBFORTEST})

add_executable (fmi1_xml_parsing_test ${RTTESTDIR}/FMI1/fmi1_xml_parsing_test.c)
target_link_libraries (fmi1_xml_parsing_test ${FMILIBFORTEST} )
if(FMILIB_TEST_LOCALE)
target_compile_definitions(fmi1_xml_parsing_test PRIVATE -DFMILIB_TEST_LOCALE)
endif()

add_executable (fmi_import_xml_test ${RTTESTDIR}/FMI1/fmi_import_xml_test.c)
target_link_libraries (fmi_import_xml_test ${FMILIBFORTEST} )

Expand Down Expand Up @@ -117,7 +121,15 @@ set(logger_output_file "${TEST_OUTPUT_FOLDER}/fmi1_logger_test_output.txt")
set(logger_reference_file "${RTTESTDIR}/FMI1/fmi1_logger_test_output.txt")

add_test(ctest_fmi1_logger_test_run fmi1_logger_test ${FMU_ME_PATH} ${FMU_TEMPFOLDER} ${logger_output_file})
add_test(ctest_fmi1_logger_test_check ${CMAKE_COMMAND} -E compare_files ${logger_output_file} ${logger_reference_file})

if(NOT CMAKE_GENERATOR STREQUAL "MSYS Makefiles")
# Skip test for MinGW, since we know it won't pass due to issues with long log messages and vsnprintf.
add_test(ctest_fmi1_logger_test_check ${CMAKE_COMMAND} -E compare_files ${logger_output_file} ${logger_reference_file})
SET_TESTS_PROPERTIES (
ctest_fmi1_logger_test_check
PROPERTIES DEPENDS ctest_fmi1_logger_test_run
)
endif()

set_target_properties(
fmi_import_me_test
Expand All @@ -130,11 +142,6 @@ set_target_properties(
fmi1_import_default_experiment_test
PROPERTIES FOLDER "Test/FMI1")

SET_TESTS_PROPERTIES (
ctest_fmi1_logger_test_check
PROPERTIES DEPENDS ctest_fmi1_logger_test_run
)

if(FMILIB_BUILD_BEFORE_TESTS)
SET_TESTS_PROPERTIES (
ctest_fmi_import_me_test
Expand Down
6 changes: 1 addition & 5 deletions Config.cmake/test_fmi2.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,7 @@ to_native_c_path("${TEST_OUTPUT_FOLDER}/${FMU2_DUMMY_CS_MODEL_IDENTIFIER}_mf.fmu
add_executable (fmi2_xml_parsing_test ${RTTESTDIR}/FMI2/fmi2_xml_parsing_test.c)
target_link_libraries (fmi2_xml_parsing_test ${FMILIBFORTEST})
if(FMILIB_TEST_LOCALE)
set(FMI2_XML_PARSING_TEST_DEFINITIONS -DFMILIB_TEST_LOCALE)
if(UNIX)
list(APPEND FMI2_XML_PARSING_TEST_DEFINITIONS -D_GNU_SOURCE)
endif()
target_compile_definitions(fmi2_xml_parsing_test PRIVATE ${FMI2_XML_PARSING_TEST_DEFINITIONS})
target_compile_definitions(fmi2_xml_parsing_test PRIVATE -DFMILIB_TEST_LOCALE)
endif()

add_executable (fmi2_import_xml_test ${RTTESTDIR}/FMI2/fmi2_import_xml_test.cc)
Expand Down
13 changes: 10 additions & 3 deletions Jenkinsfile.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,21 @@ def Configs = [
'win64': [
name: 'win64',
os: 'windows',
node: 'VisualStudio2010 && OCT-SDK-1.4',
node: 'VisualStudio2010 && OCT-SDK-1.5',
target_install: 'install',
target_test: 'test'
],
'win64_static_runtime': [
name: 'win64_static_runtime',
os: 'windows',
node: 'VisualStudio2010 && OCT-SDK-1.4',
node: 'VisualStudio2010 && OCT-SDK-1.5',
target_install: 'install',
target_test: 'test'
],
'mingw_w64': [
name: 'mingw_w64',
os: 'windows',
node: 'OCT-SDK-1.5',
target_install: 'install',
target_test: 'test'
],
Expand All @@ -34,7 +41,7 @@ def Configs = [
]
]

def version = '2.0.4-SNAPSHOT'
def version = '2.0.4-SNAPSHOT' // TODO: seems unused; remove

// Loads the 'signBinaries' function
library 'ModelonCommon@trunk'
Expand Down
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ generate:
-DFMILIB_BUILD_WITH_STATIC_RTLIB=$(BUILD_WITH_STATIC_RTLIB) \
-DFMILIB_BUILD_TESTS=$(BUILD_TESTS) \
-DFMILIB_TEST_LOCALE=$(TEST_LOCALE) \
$(FMILIB_CMAKE_CUSTOM_FLAGS) \
-G $(GENERATOR) \
../$(SRC_DIR)

Expand Down
5 changes: 5 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ The release notes are typically a highlighting subset of all changes made. For f

Note that version 2.1 is the first version with release notes. Please see the commit history for older versions.

## 2.2.2

- Bug fix: Fix build issues introduced in 2.2.1 for non-MSVC/Linux
- Bug fix: Correctly parse doubles when locale is not using decimal point, now also for FMI1

## 2.2.1

- Bug fix: Correctly parse doubles when locale is not using decimal point
Expand Down
143 changes: 142 additions & 1 deletion Test/FMI1/fmi1_xml_parsing_test.c
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
#include <stdlib.h>
#include <fmilib.h>
#include <stdio.h>
#include <stdarg.h>
#include "config_test.h"
#include <locale.h>

static const int NO_LOG_EXPECTED_MSG = 0;
static const int DO_LOG_EXPECTED_MSG = 1;
Expand All @@ -10,6 +12,17 @@ static int did_log_expected_msg;
static char *expected_message = "Invalid structured ScalarVariable name";
static char *name_check_test_directory;

static void fail(const char* fmt, ...) {
va_list args;
va_start(args, fmt);
printf("Test failure: ");
vprintf(fmt, args);
printf("\n");
va_end(args);

exit(CTEST_RETURN_FAIL);
}

char *concat(char *s1, char *s2)
{
size_t len1 = strlen(s1);
Expand Down Expand Up @@ -430,12 +443,136 @@ void test_variable_no_type(void)
parser_log_expected_message("incorrect/variable_no_type");
}

static fmi1_import_t* parse_xml(jm_callbacks* cb, const char* xmldir) {
fmi_import_context_t* context;
fmi1_import_t* xml;

context = fmi_import_allocate_context(cb);

xml = fmi1_import_parse_xml(context, xmldir);
fmi_import_free_context(context);

return xml;
}

/**
* Tests that parsing works as expected, and that the previous locale and
* thread settings are reset.
*
* Test is by default disabled, because it requires the target machine to have
* specific language packs.
*/
static void test_locale_lc_numeric() {

jm_callbacks* cb = jm_get_default_callbacks();
char* loc_old = NULL;
char* tmp = NULL;

/* Any locale that uses decimal coma instead of decimal point. */
#ifdef WIN32
char* locale_bad = "Swedish_Sweden.1252";
#else
char* locale_bad = "sv_SE.utf8";
#endif

/* Set/get thread-specific settings (and later check that they are
* restored).
* Do nothing for Linux since I don't think it's possible to check equality
* of locale_t.
*/
#ifdef _MSC_VER
int thread_setting = _DISABLE_PER_THREAD_LOCALE;
_configthreadlocale(thread_setting);
#endif

/* NOT MT-SAFE: But it's the only way to test it for Linux. There are
* currently no other tests that modify the locale globally, so should be
* OK. Also, ctest tests are by default not run in parallel.
*/
tmp = setlocale(LC_NUMERIC, locale_bad);
if (!tmp) {
/* If this errors, it's possible that your machine doesn't have
* the locale installed.
*
* Windows: It seemed like I had at least Danish, French, Swedish
* installed by default.
*
* Linux (Ubuntu 18): I had to install a language pack to get this.
*/
fail("failed to set locale");
}

/*
* Value of 'tmp' returned from 'setlocale' may be changed by further calls
* to setlocale, and it's also possible that the returned value is not
* "string equal" to the argument (i.e. alias values for the same locale).
* To be able to later compare the restored value, we therefore must copy
* 'tmp'.
*/
loc_old = (char*)malloc(strlen(tmp) + 1);
if (!loc_old) {
fail("failed to alloc memory");
}
strcpy(loc_old, tmp);
tmp = NULL;

{
/* Perform parsing and verify that the bad global locale did not affect
* the result. */

int failed = 0;
char* xmldir = concat(name_check_test_directory, "env/locale");
fmi1_import_t* xml = parse_xml(cb, xmldir);
free(xmldir);

if (xml == NULL) {
fail("failed to parse FMU");
}

if (fmi1_import_get_default_experiment_start(xml) != 2.3 ||
fmi1_import_get_default_experiment_stop(xml) != 3.55 ||
fmi1_import_get_default_experiment_tolerance(xml) != 1e-6)
{
/* If the decimal delimiter is comma, sscanf will only parse
* until the dot. */
printf("Test failure: incorrect default experiment value\n");
failed = 1;
}

fmi1_import_free(xml);

if (failed) {
fail("... see above printed messages");
}
}

/* Cleanup and verify that locale is properly restored.
*
* Getting locale should be MT-safe if all setting of locale is done in
* per_thread context.
*/
tmp = setlocale(LC_NUMERIC, loc_old);
free(loc_old);
if (!tmp) {
fail("failed to restore locale");
} else if (strcmp(tmp, locale_bad)) {
fail("unexpected locale");
}

#ifdef _MSC_VER
if (_configthreadlocale(0) != thread_setting) {
/* This was set at the beginning of the test, and should now have been restored. */
fail("unexpected Windows thread setting");
}
#endif
}

int main(int argc, char *argv[])
{
if (argc == 2) {
name_check_test_directory = argv[1];
} else {
printf("Usage: %s <path to folder naming_conventions_xmls>\n", argv[0]);
printf("Usage: %s <path to folder 'parser_test_xmls'>\n", argv[0]);
exit(CTEST_RETURN_FAIL);
}

Expand All @@ -444,5 +581,9 @@ int main(int argc, char *argv[])
test_alias_set_error_handling();
test_variable_no_type();

#ifdef FMILIB_TEST_LOCALE
test_locale_lc_numeric();
#endif

return 0;
}
21 changes: 21 additions & 0 deletions Test/FMI1/parser_test_xmls/env/locale/modelDescription.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<fmiModelDescription
fmiVersion="1.0"
modelName="x"
guid="x"
modelIdentifier="x"
numberOfContinuousStates="0"
numberOfEventIndicators="0">

<ModelExchange
modelIdentifier="x" />

<DefaultExperiment
startTime="2.3"
stopTime="3.55"
tolerance="1e-6"
/>

<ModelVariables/>

</fmiModelDescription>
20 changes: 10 additions & 10 deletions Test/FMI2/fmi2_xml_parsing_test.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include <stdlib.h>
#include <fmilib.h>
#include <stdio.h>
#include <stdarg.h>
#include "config_test.h"
#include <locale.h>

Expand Down Expand Up @@ -203,19 +204,18 @@ static void test_locale_lc_numeric() {
#endif

/* Set/get thread-specific settings (and later check that they are
* restored). */
#ifdef WIN32
* restored).
* Do nothing for Linux since I don't think it's possible to check equality
* of locale_t.
*/
#ifdef _MSC_VER
int thread_setting = _DISABLE_PER_THREAD_LOCALE;
_configthreadlocale(thread_setting);
#else
/* Do nothing for Linux since I don't think it's possible to check equality
* of locale_t. */
#endif

/* NOT MT-SAFE: But it's the only way to test it for Linux. There are
* currently no other tests that modify the locale globally, so should be
* OK.
* Worst case we can run with the '--force-new-ctest-process' ctest flag.
* OK. Also, ctest tests are by default not run in parallel.
*/
tmp = setlocale(LC_NUMERIC, locale_bad);
if (!tmp) {
Expand Down Expand Up @@ -288,7 +288,7 @@ static void test_locale_lc_numeric() {
fail("unexpected locale");
}

#ifdef WIN32
#ifdef _MSC_VER
if (_configthreadlocale(0) != thread_setting) {
/* This was set at the beginning of the test, and should now have been restored. */
fail("unexpected Windows thread setting");
Expand All @@ -307,9 +307,9 @@ int main(int argc, char *argv[])

test_variable_naming_conventions();

#ifdef FMILIB_TEST_LOCALE
#ifdef FMILIB_TEST_LOCALE
test_locale_lc_numeric();
#endif
#endif

return 0;
}
Loading

0 comments on commit 341c2c9

Please sign in to comment.