Files
opennurbs/example_test/example_test.cpp
2024-02-15 08:00:36 -08:00

1081 lines
31 KiB
C++

/*
//
// Copyright (c) 1993-2018 Robert McNeel & Associates. All rights reserved.
// OpenNURBS, Rhinoceros, and Rhino3D are registered trademarks of Robert
// McNeel & Associates.
//
// THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY.
// ALL IMPLIED WARRANTIES OF FITNESS FOR ANY PARTICULAR PURPOSE AND OF
// MERCHANTABILITY ARE HEREBY DISCLAIMED.
//
// For complete openNURBS copyright information see <http://www.opennurbs.org>.
//
////////////////////////////////////////////////////////////////
*/
////////////////////////////////////////////////////////////////
//
// example_test.cpp
//
// Example program using the Rhino file IO toolkit. The program reads
// Rhino 3dm model files and verifies they can be saved and reread with
// no changes to core model content. The program is a console application
// that takes options, file names, and directory names as command line
// arguments.
//
////////////////////////////////////////////////////////////////////////
#include "../opennurbs_public_examples.h"
class Internal_CTestContext
{
public:
enum : unsigned int
{
sizeof_a = 4088
};
Internal_CTestContext()
{
m_a = onmalloc(sizeof_a);
}
~Internal_CTestContext()
{
onfree(m_a);
}
void SetInitialDirecory(
const char* initial_directory,
unsigned int counter
)
{
m_initial_directory = Internal_CleanPath(initial_directory);
m_initial_directory_length = m_initial_directory.Length();
m_initial_directory_counter = counter;
}
void* m_a = nullptr;
ON__INT64 m_min_delta_time = 0;
ON__INT64 m_max_delta_time = 0;
unsigned int m_file_count = 0;
unsigned int m_directory_count = 0;
unsigned int m_file_open_error_count = 0;
unsigned int m_max_directory_tree_depth = 0;
unsigned int m_max_file_count = 0;
const ON_String TextLogPathFromFullPath(
const char* full_path
) const;
private:
ON_String m_initial_directory;
unsigned int m_initial_directory_counter = 0;
int m_initial_directory_length = 0;
Internal_CTestContext(const Internal_CTestContext&) = delete;
Internal_CTestContext operator=(const Internal_CTestContext&) = delete;
const ON_String Internal_CleanPath(const char* dirty_path) const
{
const char* volume = 0;
const char* path = 0;
// Use local path in case drive, dir, file_name_stem or ext are being reused.
on_splitpath(dirty_path, &volume, &path, nullptr, nullptr);
ON_String clean_path(path);
if (clean_path.IsEmpty())
return ON_String(dirty_path);
clean_path.Replace(ON_String::Backslash, ON_String::Slash);
if (nullptr != volume && volume < path)
{
ON_String clean_volume(volume, (int)(path - volume));
return (clean_volume + clean_path);
}
return clean_path;
}
};
const ON_String Internal_CTestContext::TextLogPathFromFullPath(const char* full_path) const
{
// replace initial directory with <Initial Folder> and use / for
// the directory separator so that output files are standardized.
ON_String text_log_path;
ON_String path1 = Internal_CleanPath(full_path);
if (m_initial_directory_length > 0 &&
ON_String::EqualOrdinal(m_initial_directory, m_initial_directory_length, path1, m_initial_directory_length, true)
)
{
text_log_path
= (m_initial_directory_counter>0)
? ON_String::FormatToString("<initial folder %u>", m_initial_directory_counter)
: ON_String("<initial folder>");
text_log_path += static_cast<const char*>(path1) + m_initial_directory_length;
}
else
{
text_log_path = path1;
}
return text_log_path;
}
static const ONX_ErrorCounter Internal_TestModelRead(
ON_TextLog& text_log,
const ON_String text_log_3dm_file_name,
ON_BinaryArchive& source_archive,
bool bVerbose
)
{
ONX_ModelTest::Type test_type = ONX_ModelTest::Type::ReadWriteReadCompare;
/////
//
// Read the orginal file
//
text_log.PushIndent();
ONX_ModelTest test;
const ON_wString wide_text_log_3dm_file_name(text_log_3dm_file_name);
test.ReadTest(source_archive, test_type, true, wide_text_log_3dm_file_name, &text_log);
text_log.PrintNewLine();
text_log.Print("Test Results:\n");
text_log.PushIndent();
test.Dump(text_log);
text_log.PopIndent();
ONX_ErrorCounter error_counter = test.ErrorCounter();
const ONX_Model* source_model = test.SourceModel().get();
if (nullptr == source_model)
{
text_log.PopIndent();
return error_counter;
}
const bool bCompareTestFailed = ONX_ModelTest::Result::Fail == test.TestResult(ONX_ModelTest::Type::ReadWriteReadCompare);
if ( bVerbose || bCompareTestFailed )
{
for (int i = 0; i < 2; i++)
{
if (0 == i)
test.DumpSourceModel();
else
test.DumpReadWriteReadModel();
if (false == bCompareTestFailed)
break;
}
}
text_log.PrintNewLine();
const ON_ModelComponent::Type component_types[] =
{
// TODO uncomment types as components are derived from ON_ModelComponent
ON_ModelComponent::Type::Image,
//ON_ModelComponent::Type::TextureMapping,
ON_ModelComponent::Type::RenderMaterial,
ON_ModelComponent::Type::LinePattern,
ON_ModelComponent::Type::Layer,
//ON_ModelComponent::Type::Group,
ON_ModelComponent::Type::TextStyle,
ON_ModelComponent::Type::DimStyle,
ON_ModelComponent::Type::RenderLight,
ON_ModelComponent::Type::HatchPattern,
ON_ModelComponent::Type::InstanceDefinition,
ON_ModelComponent::Type::ModelGeometry,
//ON_ModelComponent::Type::HistoryRecord
};
const unsigned int component_type_count = (unsigned int)(sizeof(component_types) / sizeof(component_types[0]));
const ONX_Model& model = *source_model;
error_counter.ClearLibraryErrorsAndWarnings();
const ON_ComponentManifest& manifest = model.Manifest();
ON_SerialNumberMap it_map;
for (unsigned int i = 0; i < component_type_count; i++)
{
const ON_ModelComponent::Type component_type = component_types[i];
const bool bUniqueNameRequired = ON_ModelComponent::UniqueNameRequired(component_type);
const bool bEmbeddedFileComponent = (ON_ModelComponent::Type::Image == component_type);
ONX_ModelComponentIterator it(model,component_type);
unsigned int it_count = 0;
for (ON_ModelComponentReference mcr = it.FirstComponentReference(); false == mcr.IsEmpty(); mcr = it.NextComponentReference())
{
const ON_ModelComponent* model_component = mcr.ModelComponent();
if (nullptr == model_component)
{
ON_ERROR("Iterator returned nullptr mcr.ModelComponent()");
continue;
}
if (model_component->ComponentType() != component_type)
{
ON_ERROR("Iterator returned wrong mcr.ModelComponent()->ComponentType()");
continue;
}
const ON__UINT64 model_component_sn = model_component->RuntimeSerialNumber();
const ON_UUID model_component_id = model_component->Id();
if (0 == model_component_sn)
{
ON_ERROR("Iterator mcr.ModelComponent()->RuntimeSerialNumber() is zero.");
continue;
}
if (ON_nil_uuid == model_component_id)
{
ON_ERROR("Iterator mcr.ModelComponent()->Id() is nil.");
continue;
}
const ON_SerialNumberMap::SN_ELEMENT* e_sn = it_map.FindSerialNumber(model_component_sn);
if (nullptr != e_sn)
{
ON_ERROR("Iterator returned component serial number twice.");
continue;
}
const ON_SerialNumberMap::SN_ELEMENT* e_id = it_map.FindId(model_component_id);
if (nullptr != e_id)
{
ON_ERROR("Iterator returned component id twice.");
continue;
}
ON_SerialNumberMap::SN_ELEMENT* e = it_map.AddSerialNumberAndId(model_component_sn,model_component_id);
if (nullptr == e)
{
ON_ERROR("ON_SerialNumberMap failed to add sn and id.");
continue;
}
if (e->m_value.m_u_type != 0)
{
ON_ERROR("ON_SerialNumberMap error.");
continue;
}
e->m_value.m_u_type = 1;
e->m_value.m_u.ptr = (void*)model_component;
const ON_ComponentManifestItem& item = manifest.ItemFromComponentRuntimeSerialNumber(model_component_sn);
if (item.IsUnset())
{
ON_ERROR("Iterator returned item not in manifest.");
continue;
}
if (model_component_sn != item.ComponentRuntimeSerialNumber())
{
ON_ERROR("manifest.ItemFromComponentRuntimeSerialNumber() error.");
continue;
}
if (model_component_id != item.Id())
{
ON_ERROR("item has wrong id.");
continue;
}
const ON_ComponentManifestItem& item_id = manifest.ItemFromId(model_component_id);
if (&item != &item_id)
{
ON_ERROR("manifest.ItemFromId() failed.");
continue;
}
if (bUniqueNameRequired || bEmbeddedFileComponent)
{
const ON_Bitmap* embedded_file_reference = bEmbeddedFileComponent ? ON_Bitmap::Cast(model_component) : nullptr;
const ON_NameHash full_path_hash
= (nullptr != embedded_file_reference)
? ON_NameHash::CreateFilePathHash(embedded_file_reference->FileReference())
: ON_NameHash::EmptyNameHash;
const ON_NameHash name_hash
=bEmbeddedFileComponent
? full_path_hash
: model_component->NameHash();
if (false == name_hash.IsValidAndNotEmpty())
{
ON_ERROR("model compoent name is not valid.");
continue;
}
if (name_hash != item.NameHash())
{
ON_ERROR("item has wrong name hash.");
continue;
}
const ON_ComponentManifestItem& item_name = manifest.ItemFromNameHash(component_type,name_hash);
if (&item != &item_name)
{
ON_ERROR("manifest.ItemFromNameHash() failed.");
continue;
}
}
it_count++;
}
if (it_count != manifest.ActiveAndDeletedComponentCount(component_type))
{
ON_ERROR("iterator count != manifest.ActiveAndDeletedComponentCount(component_type)");
continue;
}
}
text_log.PopIndent();
error_counter.AddLibraryErrorsAndWarnings();
return error_counter;
}
static const ONX_ErrorCounter Internal_TestFileRead(
ON_TextLog& text_log,
const ON_String fullpath,
const ON_String text_log_path,
bool bVerbose
)
{
FILE* fp = nullptr;
fp = ON_FileStream::Open3dmToRead(fullpath);
ONX_ErrorCounter error_counter;
error_counter.ClearLibraryErrorsAndWarnings();
const ON_String path_to_print
= (text_log_path.IsNotEmpty())
? text_log_path
: fullpath;
for (;;)
{
if (nullptr == fp)
{
text_log.Print(
"Skipped file: %s\n",
static_cast<const char*>(path_to_print)
);
error_counter.IncrementFailureCount();
break;
}
text_log.Print(
"File name: %s\n",
static_cast<const char*>(path_to_print)
);
ON_BinaryFile source_archive(ON::archive_mode::read3dm, fp);
const ON_wString wide_full_path(fullpath);
source_archive.SetArchiveFullPath(static_cast<const wchar_t*>(wide_full_path));
error_counter += Internal_TestModelRead(text_log, path_to_print, source_archive, bVerbose);
break;
}
if ( nullptr != fp )
{
ON_FileStream::Close(fp);
fp = nullptr;
}
return error_counter;
}
static int Internal_ComparePath(const ON_String* lhs, const ON_String* rhs)
{
// sort nullptr to end of list.
if (lhs == rhs)
return 0;
if (nullptr == lhs)
return 1;
if (nullptr == rhs)
return -1;
int rc = ON_String::CompareOrdinal(static_cast<const char*>(*lhs), static_cast<const char*>(*rhs), true);
if ( 0 == rc )
rc = ON_String::CompareOrdinal(static_cast<const char*>(*lhs), static_cast<const char*>(*rhs), false);
return rc;
}
static const ONX_ErrorCounter Internal_TestReadFolder(
ON_TextLog& text_log,
const char* directory_path,
unsigned int directory_tree_depth,
bool bVerbose,
Internal_CTestContext& test_context
)
{
ONX_ErrorCounter error_counter;
error_counter.ClearLibraryErrors();
if (nullptr == directory_path || 0 == directory_path[0])
{
text_log.Print("Empty directory name.\n");
}
ON_FileIterator fit;
if (false == fit.Initialize(directory_path))
{
text_log.Print(
"Invalid directory name: %s\n",
directory_path
);
error_counter.IncrementFailureCount();
return error_counter;
}
const ON_String text_log_directory_name
= (directory_tree_depth <= 1)
? ON_String(directory_path)
: test_context.TextLogPathFromFullPath(directory_path);
text_log.Print(
"Directory name: %s\n",
static_cast<const char*>(text_log_directory_name)
);
text_log.PushIndent();
ON_ClassArray< ON_String > sub_directories(32);
ON_ClassArray< ON_String > skipped_files(32);
ON_ClassArray< ON_String > tested_files(32);
for ( bool bHaveItem = fit.FirstItem(); bHaveItem; bHaveItem = fit.NextItem() )
{
if ( test_context.m_max_file_count > 0 && test_context.m_file_count >= test_context.m_max_file_count)
break;
if (fit.CurrentItemIsDirectory())
{
if (directory_tree_depth < test_context.m_max_directory_tree_depth)
{
sub_directories.Append(fit.CurrentItemFullPathName());
}
continue;
}
ON_String fullpath( fit.CurrentItemFullPathName() );
if (ON_FileStream::Is3dmFile(fullpath, false))
tested_files.Append(fullpath);
else
skipped_files.Append(fullpath);
}
// sort file and folder names so test order depends only on content.
// This is required so results from different computers can be compared.
sub_directories.QuickSort(Internal_ComparePath);
skipped_files.QuickSort(Internal_ComparePath);
tested_files.QuickSort(Internal_ComparePath);
if (skipped_files.Count() > 0)
{
text_log.PrintNewLine();
for (int i = 0; i < skipped_files.Count(); i++)
{
const ON_String path_to_print = test_context.TextLogPathFromFullPath(skipped_files[i]);
text_log.Print(
"Skipped file: %s\n",
static_cast<const char*>(path_to_print)
);
}
text_log.PrintNewLine();
}
for ( int i = 0; i < tested_files.Count(); i++ )
{
const ON_String full_path = tested_files[i];
const ON_String path_to_print = test_context.TextLogPathFromFullPath(full_path);
test_context.m_file_count++;
const ONX_ErrorCounter file_error_counter = Internal_TestFileRead(text_log, full_path, path_to_print, bVerbose);
error_counter += file_error_counter;
}
for (int i = 0; i < sub_directories.Count(); i++)
{
if (test_context.m_max_file_count > 0 && test_context.m_file_count >= test_context.m_max_file_count)
break;
const ON_String sub_directory_path = sub_directories[i];
test_context.m_directory_count++;
error_counter += Internal_TestReadFolder(text_log, sub_directory_path, directory_tree_depth + 1, bVerbose, test_context);
}
text_log.PopIndent();
return error_counter;
}
static ONX_ErrorCounter Internal_InvalidArg(const ON_String arg, ON_TextLog& text_log)
{
ONX_ErrorCounter err = ONX_ErrorCounter::Zero;
text_log.Print("Invalid command line parameter: \"%s\"\n", static_cast<const char*>(arg));
err.IncrementFailureCount();
return err;
}
static ONX_ErrorCounter Internal_Test(
unsigned int max_directory_tree_depth,
ON_String full_path,
bool bVerbose,
ON_TextLog& text_log,
unsigned int& directory_counter,
unsigned int& file_count,
unsigned int& folder_count
)
{
ONX_ErrorCounter err = ONX_ErrorCounter::Zero;
if (ON_FileSystem::IsFile(full_path))
{
if (ON_FileStream::Is3dmFile(full_path, false))
{
text_log.Print("Testing 3dm file: %s\n", static_cast<const char*>(full_path));
err = Internal_TestFileRead(text_log, full_path, ON_String::EmptyString, bVerbose);
file_count++;
}
}
else if (ON_FileSystem::IsDirectory(full_path))
{
if ( max_directory_tree_depth > 0 )
{
text_log.Print("Testing 3dm files in folder: %s\n", static_cast<const char*>(full_path));
Internal_CTestContext test_context;
directory_counter++;
test_context.SetInitialDirecory(full_path,directory_counter);
test_context.m_max_directory_tree_depth = max_directory_tree_depth;
err = Internal_TestReadFolder(text_log, full_path, 1, bVerbose, test_context);
file_count += test_context.m_file_count;
folder_count += test_context.m_directory_count;
}
}
else
{
err += Internal_InvalidArg(full_path,text_log);
}
return err;
}
static ON_String Internal_PlatformId(bool bVerbose)
{
const ON_String runtime =
#if defined(ON_RUNTIME_WIN)
bVerbose ? "Windows" : "Win"
#elif defined(ON_RUNTIME_APPLE_IOS)
bVerbose ? "Apple iOS" : "iOS"
#elif defined(ON_RUNTIME_APPLE_MACOS)
bVerbose ? "Apple Mac OS" : "MacOS"
#elif defined(ON_RUNTIME_APPLE)
"Apple"
#elif defined(ON_RUNTIME_ANDROID)
"Android"
#elif defined(ON_RUNTIME_LINUX)
"Linux"
#else
"Runtime"
#endif
;
const ON_String platform =
#if defined(ON_64BIT_RUNTIME)
bVerbose ? " 64 bit" : "64"
#elif defined(ON_32BIT_RUNTIME)
bVerbose ? " 32 bit" : "32"
#else
""
#endif
;
const ON_String configuration =
#if defined(ON_DEBUG)
bVerbose ? " Debug" : "Debug"
#else
bVerbose ? " Release" : "Release"
#endif
;
ON_String platform_id(runtime+platform+configuration);
return platform_id;
}
static ON_String Internal_DefaultOutFileName(
const ON_String exe_stem
)
{
ON_String default_file_name(exe_stem);
default_file_name.TrimLeftAndRight();
if (default_file_name.IsEmpty())
default_file_name = "example_test";
default_file_name += "_log";
const ON_String platform_id = Internal_PlatformId(false);
if (platform_id.IsNotEmpty())
default_file_name += ON_String(ON_String("_") + platform_id);
default_file_name += ".txt";
return default_file_name;
}
static void Internal_PrintHelp(
const ON_String example_test_exe_stem,
ON_TextLog& text_log
)
{
const ON_String default_out_filename = Internal_DefaultOutFileName(example_test_exe_stem);
text_log.Print("\n");
text_log.Print("SYNOPSIS:\n");
text_log.Print(" %s [-out[:outfilename.txt]] [-recursive[:N]] [-verbose] <file or folder names>\n", static_cast<const char*>(example_test_exe_stem) );
text_log.Print("\n");
text_log.Print("DESCRIPTION:\n");
text_log.Print(" If a file is listed, it is read as an opennurbs model file.\n");
text_log.Print(" If a folder is listed, all .3dm files in that folder are read\n");
text_log.Print(" as opennurbs model files.\n");
text_log.Print("\n");
text_log.Print(" Available options:\n");
text_log.Print(" -help\n");
text_log.Print(" Displays this message.\n");
text_log.Print(" -out\n");
text_log.Print(" If -out is not present, output is written to stdout.\n");
text_log.Print(" If :outfilename.txt is appended to -out, output is written to the specifed file.\n");
text_log.Print(" If :stdout is appended to -out, output is sent to stdout.\n");
text_log.Print(" If :null is appended to -out, no output is written.\n");
text_log.Print(" Otherise output is written to %s.\n",static_cast<const char*>(default_out_filename));
text_log.Print(" -recursive\n");
text_log.Print(" -r\n");
text_log.Print(" Recursivly reads files in subfolders.\n");
text_log.Print(" If a :N is appended to the option, N limits the recursion depth.\n");
text_log.Print(" -recursive:1 reads all 3dm files in the folder and does not recurse.\n");
text_log.Print(" -verbose\n");
text_log.Print(" -v\n");
text_log.Print(" For each 3dm file, a text summary of the 3dm file contents is created\n");
text_log.Print(" in the folder containing the 3dm file.\n");
text_log.Print("\n");
text_log.Print("RETURNS:\n");
text_log.Print(" 0: All tested files passed.\n");
text_log.Print(" >0: Number of failures, errors, or warnings. See output for details.\n");
text_log.Print("\n");
text_log.Print("EXAMPLE:\n");
text_log.Print(" %s -out:mytestresults.txt -resursive:2 .../example_files\n",static_cast<const char*>(example_test_exe_stem));
text_log.Print(" with read all the opennurbs .3dm files in the .../example_files folder\n");
text_log.Print(" and immediate subfolders of .../example_files. Output will be saved in mytestresults.txt.\n");
}
static void Internal_PrintHelp(
const ON_String example_test_exe_stem,
ON_TextLog* text_log_ptr
)
{
ON_TextLog print_to_stdout;
if (nullptr == text_log_ptr)
{
print_to_stdout.SetIndentSize(2);
text_log_ptr = &print_to_stdout;
}
return Internal_PrintHelp(example_test_exe_stem, *text_log_ptr);
}
static char Internal_ParseOptionTailSeparator()
{
return ':';
}
static const char* Internal_ParseOptionHead(
const char* s,
const char* option_name0,
const char* option_name1 = nullptr,
const char* option_name2 = nullptr
)
{
if (nullptr == s || 0 == s[0])
return nullptr;
if ('-' != s[0] )
{
#if defined(ON_OS_WINDOWS)
if ( '/' != s[0] )
#endif
return nullptr;
}
s++;
if (0 == s[0])
return nullptr;
const char* option_names[] = { option_name0, option_name1, option_name2 };
const size_t option_name_count = sizeof(option_names) / sizeof(option_names[0]);
for (size_t i = 0; i < option_name_count; i++)
{
const char* option_name = option_names[i];
const int option_name_length = ON_String::Length(option_name);
if (option_name_length < 1)
continue;
if (ON_String::EqualOrdinal(option_name, option_name_length, s, option_name_length, true))
{
const char* tail = s + option_name_length;
if (0 == tail[0] || Internal_ParseOptionTailSeparator() == tail[0])
return tail;
}
}
return nullptr;
}
static const ON_String Internal_ParseOptionTail(
const char* s
)
{
for (;;)
{
if (nullptr == s || Internal_ParseOptionTailSeparator() != s[0] || s[1] <= ON_String::Space)
break;
ON_String tail(s+1);
tail.TrimRight();
int len = tail.Length();
if (len < 1)
break;
if (len >= 2)
{
bool bTrim = false;
if ('"' == tail[0] && '"' == tail[len - 1])
bTrim = true;
else if ('\'' == tail[0] && '\'' == tail[len - 1])
bTrim = true;
if (bTrim)
{
tail = ON_String(s + 2, len - 2);
if (tail.Length() < 1)
break;
}
}
return tail;
}
return ON_String::EmptyString;
}
static bool Internal_ParseArg_HELP(const ON_String arg)
{
const char* tail = Internal_ParseOptionHead(static_cast<const char*>(arg), "help", "h", "?" );
return (nullptr != tail && 0 == tail[0]);
}
static bool Internal_ParseArg_VERBOSE(const ON_String arg)
{
const char* tail = Internal_ParseOptionHead(static_cast<const char*>(arg), "verbose", "v" );
return (nullptr != tail && 0 == tail[0]);
}
static bool Internal_ParseArg_OUT(const ON_String exe_stem, const ON_String arg, ON_String& output_file_path)
{
const char* tail = Internal_ParseOptionHead(static_cast<const char*>(arg), "out" );
if (nullptr == tail)
return false;
if (0 == tail[0])
{
output_file_path = Internal_DefaultOutFileName(exe_stem);
}
else if (Internal_ParseOptionTailSeparator() == tail[0])
{
output_file_path = ON_FileSystemPath::ExpandUser(Internal_ParseOptionTail(tail));
output_file_path.TrimLeftAndRight();
if (
false == output_file_path.EqualOrdinal("stdout",true)
&& false == output_file_path.EqualOrdinal("stderr",true)
&& false == output_file_path.EqualOrdinal("null",true)
&& false == output_file_path.EqualOrdinal("dev/null",true)
&& ON_FileSystem::IsDirectory(output_file_path)
)
{
const ON_wString wide_dir(output_file_path);
const ON_wString wide_fname(Internal_DefaultOutFileName(exe_stem));
output_file_path = ON_String(ON_FileSystemPath::CombinePaths(wide_dir, false, wide_fname, true, false));
}
}
else
{
output_file_path = ON_String::EmptyString;
}
return true;
}
static bool Internal_ParseArg_RECURSE(const ON_String arg, unsigned int& N)
{
const char* tail = Internal_ParseOptionHead(static_cast<const char*>(arg), "recursive", "r" );
if (nullptr == tail)
return false;
if (0 == tail[0])
{
N = 16; // sanity limit for default directory recursion depth;
return true;
}
if (Internal_ParseOptionTailSeparator() == tail[0])
{
const ON_String num = Internal_ParseOptionTail(tail);
if (num.IsNotEmpty())
{
unsigned int u = 0;
const char* s1 = ON_String::ToNumber(num, u, &u);
if (nullptr != s1 && s1 > static_cast<const char*>(num) && u >= 1 && 0 == s1[0])
{
N = u;
return true;
}
}
}
N = 0;
return false;
}
static const ON_String Internal_ParseArg_PATH(const ON_String arg, unsigned int max_directory_tree_depth)
{
ON_String arg_full_path = ON_FileSystemPath::ExpandUser(static_cast<const char*>(arg));
arg_full_path.TrimLeftAndRight();
if (ON_FileSystem::IsFile(arg_full_path))
return arg_full_path;
if (max_directory_tree_depth > 0)
{
if (arg_full_path.Length() != 1 || false == ON_FileSystemPath::IsDirectorySeparator(arg_full_path[0], true))
{
const char dir_seps[3] = { ON_FileSystemPath::DirectorySeparatorAsChar, ON_FileSystemPath::AlternateDirectorySeparatorAsChar, 0 };
arg_full_path.TrimRight(dir_seps);
}
if (ON_FileSystem::IsDirectory(arg_full_path))
return arg_full_path;
}
return ON_String::EmptyString;
}
static void Internal_PrintIntroduction(
const ON_String& example_test_exe_path,
ON_TextLog& text_log
)
{
text_log.Print("Executable: %s\n", static_cast<const char*>(example_test_exe_path));
text_log.Print("Compiled: " __DATE__ " " __TIME__ "\n");
const ON_String platform_name = Internal_PlatformId(true);
ON::Version();
text_log.Print("Platform: %s\n", static_cast<const char*>(platform_name));
text_log.Print(
"Opennurbs version: %u.%u %04u-%02u-%02u %02u:%02u (%u)\n",
ON::VersionMajor(),
ON::VersionMinor(),
ON::VersionYear(),
ON::VersionMonth(),
ON::VersionDayOfMonth(),
ON::VersionHour(),
ON::VersionMinute(),
ON::VersionBranch()
);
text_log.Print("Current time: ");
text_log.PrintCurrentTime();
text_log.Print(" UCT\n");
}
int main( int argc, const char *argv[] )
{
// If you are using OpenNURBS as a Windows DLL, then you MUST use
// ON::OpenFile() to open the file. If you are not using OpenNURBS
// as a Windows DLL, then you may use either ON::OpenFile() or fopen()
// to open the file.
ON_ErrorLog verbose_log;
verbose_log.EnableLogging();
ON_String arg(nullptr != argv ? argv[0] : ((const char*)nullptr));
arg.TrimLeftAndRight();
const ON_String example_test_exe_path(arg);
ON_String example_test_exe_stem;
ON_FileSystemPath::SplitPath(example_test_exe_path, nullptr, nullptr, &example_test_exe_stem, nullptr);
if (example_test_exe_stem.IsEmpty())
example_test_exe_stem = "example_test";
ON_TextLog print_to_stdout;
print_to_stdout.SetIndentSize(2);
int argi;
if ( argc < 2 )
{
Internal_PrintHelp(example_test_exe_stem, print_to_stdout);
return 0;
}
// Call once in your application to initialze opennurbs library
ON::Begin();
// default text_log is to stdout
ON_TextLog* text_log = &print_to_stdout;
FILE* text_log_fp = nullptr;
unsigned int maximum_directory_depth = 0;
bool bVerbose = false; // true = output will include a full listing of all 3dm file contents.
ONX_ErrorCounter err;
unsigned int file_count = 0;
unsigned int folder_count = 0;
unsigned int directory_arg_counter = 0;
bool bPrintIntroduction = true;
for ( argi = 1; argi < argc; argi++ )
{
arg = argv[argi];
arg.TrimLeftAndRight();
if (arg.IsEmpty())
continue;
if (argi + 1 == argc && Internal_ParseArg_HELP(arg))
{
Internal_PrintHelp(example_test_exe_stem, text_log);
if ( 0 == folder_count && 0 == file_count && 0 == err.TotalCount() )
return 0;
continue;
}
// check for -out:<file name> option
ON_String output_file_name;
if ( Internal_ParseArg_OUT(example_test_exe_stem, arg, output_file_name) )
{
// need a new introduction when output destination changes.
bPrintIntroduction = true;
// change destination of text_log file
if ( text_log != &print_to_stdout && text_log != &ON_TextLog::Null )
{
delete text_log;
}
if ( text_log_fp )
{
ON::CloseFile(text_log_fp);
text_log_fp = nullptr;
}
text_log = &print_to_stdout;
if (output_file_name.IsEmpty() || output_file_name.EqualOrdinal("stdout", true))
continue;
if (output_file_name.EqualOrdinal("null", true) || output_file_name.EqualOrdinal("dev/null", true))
{
text_log = &ON_TextLog::Null;
continue;
}
FILE* text_fp = ON::OpenFile(output_file_name,"w");
if (nullptr == text_fp)
{
err += Internal_InvalidArg(arg, *text_log);
break;
}
text_log_fp = text_fp;
text_log = new ON_TextLog(text_log_fp);
text_log->SetIndentSize(2);
continue;
}
// check for -recursive:<directory depth> option
if (Internal_ParseArg_RECURSE(arg, maximum_directory_depth))
{
continue;
}
if (Internal_ParseArg_VERBOSE(arg))
{
bVerbose = true;
continue;
}
if (bPrintIntroduction)
{
bPrintIntroduction = false;
Internal_PrintIntroduction(example_test_exe_path, *text_log);
}
const ON_String arg_full_path = Internal_ParseArg_PATH(arg,maximum_directory_depth);
if (arg_full_path.IsEmpty())
{
err += Internal_InvalidArg(arg, *text_log);
break;
}
err += Internal_Test(
maximum_directory_depth,
arg_full_path,
bVerbose,
*text_log,
directory_arg_counter,
file_count,
folder_count
);
}
if (bPrintIntroduction)
{
bPrintIntroduction = false;
Internal_PrintIntroduction(example_test_exe_path, *text_log);
}
if (folder_count > 0)
{
text_log->Print(
"Tested %u 3dm files in %u folders.",
file_count,
folder_count
);
}
else
{
text_log->Print(
"Tested %u 3dm files.",
file_count
);
}
if (err.FailureCount() > 0)
text_log->Print(" Failures. ");
else if (err.ErrorCount() > 0) {
text_log->Print(" Errors:\n");
for (int vbli = 0; vbli < verbose_log.Count(); vbli++) {
text_log->Print(" !! ");
text_log->Print(verbose_log.Event(vbli).Description());
text_log->Print("\n");
}
}
else if (err.FailureCount() > 0)
text_log->Print(" Warnings. ");
else
text_log->Print(" All tests passed. ");
err.Dump(*text_log);
text_log->PrintNewLine();
if ( text_log != &print_to_stdout && text_log != &ON_TextLog::Null )
{
delete text_log;
}
text_log = nullptr;
if ( text_log_fp )
{
// close the text text_log file
ON::CloseFile( text_log_fp );
text_log_fp = 0;
}
// OPTIONAL: Call just before your application exits to clean
// up opennurbs class definition information.
// Opennurbs will not work correctly after ON::End()
// is called.
ON::End();
return err.TotalCount();
}