Merge remote-tracking branch 'origin/master' into docs

This commit is contained in:
reaction.la 2022-06-16 08:42:08 +10:00
commit d95c4a1c71
No known key found for this signature in database
GPG Key ID: 99914792148C8388
20 changed files with 285 additions and 87 deletions

55
.gitattributes vendored
View File

@ -38,32 +38,37 @@ Makefile text eol=lf encoding=utf-8
# Force binary files to be binary
# Archives
*.7z filter=lfs diff=lfs merge=lfs -text
*.br filter=lfs diff=lfs merge=lfs -text
*.gz filter=lfs diff=lfs merge=lfs -text
*.tar filter=lfs diff=lfs merge=lfs -text
*.zip filter=lfs diff=lfs merge=lfs -text
# Documents
*.pdf filter=lfs diff=lfs merge=lfs -text
# Images
*.gif filter=lfs diff=lfs merge=lfs -text
*.ico filter=lfs diff=lfs merge=lfs -text
*.jpg filter=lfs diff=lfs merge=lfs -text
*.jpeg filter=lfs diff=lfs merge=lfs -text
*.pdf filter=lfs diff=lfs merge=lfs -text
*.png filter=lfs diff=lfs merge=lfs -text
*.psd filter=lfs diff=lfs merge=lfs -text
*.webp filter=lfs diff=lfs merge=lfs -text
# Fonts
*.woff2 filter=lfs diff=lfs merge=lfs -text
# Other
*.exe filter=lfs diff=lfs merge=lfs -text
###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs diff=csharp
###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln merge=binary
#*.csproj merge=binary
#*.vbproj merge=binary
#*.vcxproj merge=binary
#*.vcproj merge=binary
#*.dbproj merge=binary
#*.fsproj merge=binary
#*.lsproj merge=binary
#*.wixproj merge=binary
#*.modelproj merge=binary
#*.sqlproj merge=binary
#*.wwaproj merge=binary
###############################################################################
# diff behavior for common document formats
#

View File

@ -11,3 +11,6 @@
alias = ! git config --get-regexp ^alias\\. | sed -e s/^alias\\.// -e s/\\ /\\ =\\ / | grep -v ^'alias ' | sort
[commit]
gpgSign = true
[submodule]
recurse = true

12
.gitmodules vendored
View File

@ -1,11 +1,9 @@
[submodule "libsodium"]
path = libsodium
url = https://github.com/jedisct1/libsodium.git
ignore = dirty
[submodule "wxWidgets"]
path = wxWidgets
url = https://github.com/wxWidgets/wxWidgets.git
ignore = dirty
url = cpal.pw:~/libsodium.git
[submodule "mpir"]
path = mpir
url = https://github.com/BrianGladman/mpir.git
url = cpal.pw:~/mpir.git
[submodule "wxWidgets"]
path = wxWidgets
url = cpal.pw:~/wxWidgets.git

View File

@ -25,11 +25,11 @@ namespace ro {
// copy constructor
sql(const sql& a) = delete;
// move constructor
sql(sql&& p) :std::unique_ptr<Icompiled_sql>(p.release()) { }
sql(sql&& p) noexcept :std::unique_ptr<Icompiled_sql>(p.release()) { }
// copy assignment
sql& operator=(const sql) = delete;
// Move assignment
sql& operator=(sql&& p) {
sql& operator=(sql&& p) noexcept {
std::unique_ptr<Icompiled_sql>::reset(p.release());
}
sql(Icompiled_sql* p) :std::unique_ptr<Icompiled_sql>(p) {}

View File

@ -102,7 +102,7 @@ void display_wallet::OnClose(wxCloseEvent& event) {
// and to object to the closing in a "file not saved" type situation.
// https://docs.wxwidgets.org/trunk/classwx_close_event.html
DestroyChildren();
Destroy(); //Default handler will destroy the window. This is our handler for the user calling close,
Destroy(); //Default handler will destroy the window. This is our handler for the user calling close,
// replacing the default handler.'
if (singletonFrame->m_panel ==this)singletonFrame->m_panel = nullptr;

View File

@ -102,6 +102,105 @@ repository results in non portable surprises and complexity. Makes it hard
for anyone else to build your project, because they will have to, by hand,
tell your project where the libraries are on their system.
When one is developing code, you normally have a git branch. But
the git commit of the master project in which the submodule is
contained does not notice its subproject has changed, unless the
subproject head has changed. And the subject project head will not
change if it points to a name, rather than to a particular commit. For
ones changes to a submodule to be reflected in the master project in
any consistent or predictable way, the submodule has to be in
detached head mode, with the head pointing directly to a commit,
rather than pointing to a branch that points to a commit.
Git commands in master project do not look inside the subproject.
They just look at the subproject's head.
This means that signing off on changes to a submodule is
irrelevant. One signs off on the master project, which includes the
hash of that submodule commit.
When one is changing submodules for the use of a particular
project, making related changes in the master project and
submodules, one should not track the changes by creating and
updating branch names in the submodule, but by creating and
updating branch names in the containing module, so that the
commits in the submodule have no name in the submodule, the
submodule is always in detached head state, albeit the head may be
tagged. Names in submodules are primarily of value for
amendments to the submodule as an independent module,
intended to be used by multiple projects, and for this purpose, tags
are better than branch names. wxWidgets releases are identified by
tag, not by branch, and the names of branches are only used to
communicate a particular project on the submodule to other people
working on that project as their master project.
Branch names are not useful within a submodule, though
submodule may well be, from the point of view of the primary
developers, not a submodule but a module in its own right, used as
a submodule in many different modules, so for them, branch names
will be useful. But when you are modifying the submodules in a
project as a single project, making related changes in the module
and submodule, the names belong in the primary project module,
Within the submodule, commits are nameless with detached head,
the name in primary module naming a group of related commits in
several submodules, which commits do not usually receive
independent names of their own, even though the commits have to
be made within the submodule, not in the containing module which
names the complete set of interrelated commits.
In this case, working on submodules as part of a single larger project, you should set
```bash
git config --local submodule.recurse true
```
In the primary project, so that you conveniently push and pull a
group of related changes as one thing, and the build for the whole
project should treat the submodule libraries as having a
dependency on module/.git/modules/submodule/HEAD, rather than
checking every single file in the submodules every time to see
if one has changed, for there could be an enormous number of
them. The primary build should invoke the submodule build, which
*will* check each file in the submodule for changes, only when the
submodule detached head has changed. And therefore, you want it
to change, you want the submodule head to be nameless and
detached, whenever you modify a submodule as part of a larger
project where you test your changes by rebuilding the whole
project to make sure all your related changes fit together.
When tracking an upstream submodule that has submodules of its
own, which have their own upstreams
Update your version with
```bash
git pull upstream --recurse-submodules=on-demand «their-latest-release»
```
Make sure things still work. Get everything working. (You do have unit test, right?)
then:
```bash
git submodule foreach --recursive 'git push origin HEAD:«your-tracking-branch»'
git submodule foreach --recursive 'git switch --detach'
```
All of which, of course, presupposes you have already set unit tests,
upstream, origin, and your tracking branch appropriately.
Even if your local modifications are nameless in your local
submodule repository, on your remote submodule repository they
need to have a name to be pushed to, hence you need to have a
tracking branch in each of your remote images of each of your
submodules, and that tracking branch will need to point to the root
of a tree of all the nameless commits that the names and commits
in your superproject that contains this submodules point to.
You want `.gitmodules` in your local image of the repository to
reflect the location and fork of your new remote repository, with
your remote as its `origin` and their remote as its `upstream`.
You need an enormous pile of source code, the work of many people over
a very long time, and GitSubmodules allows this to scale, because the
local great big pile of source code references many independent and
@ -153,7 +252,6 @@ submodule and the next are such that one is only likely to make changes in
one module at at time.
# Passphrases
All wallets now use random words - but you cannot carry an eighteen word random phrase though an airport in you head
Should use [grammatically correct passphrases](https://github.com/lungj/passphrase_generator).

View File

@ -43,13 +43,20 @@ many of the library files, and therefore git will abort the pull.
"Contributor Code of Conduct"
{target="_blank"}
`winConfigure.bat` also configures the repository you just created to use
The winConfigure script builds everything, including the documents, but
takes a while. Normally when you make changes to the source code you
should rebuild just the program, using `wallet.sln` on windows.
To rebuild the documents after editing them, `docs/mkdocs`
winConfigure.bat also configures the repository you just created to use
`.gitconfig` in the repository, causing git to to implement GPG signed
commits -- because [cryptographic software is under attack] from NSA
entryists and shills, who seek to introduce backdoors.
This may be inconvenient if you do not have `gpg` installed and set up.
It also means that subsequent pulls and merges will require you to have `gpg `trust the key `public_key.gpg`, and if you submit a pull request, the puller will need to trust your `gpg` public key.
`.gitconfig` adds several git aliases:
1. `git lg` to display the gpg trust information for the last four commits.

View File

@ -219,7 +219,7 @@ void Frame::OnDeleteConfiguration(wxCommandEvent&)
using ro::bin2hex, ro::to_base64_string;
void Frame::NewWallet(wxFileName& filename, ristretto255::hash<256>& secret) {
/*If creation fails, abort with exception. If it succeeds, set LastUsed to default filename.
The exception in unit test should simply generate an error message, but if run during initialization,
The exception in unit test should simply generate an error message, but if run during initialization,
should bring up the more complex UI for constructing or selecting your wallet file.*/
wxLogMessage(_wx("New wallet file %s"), filename.GetFullPath());
std::unique_ptr<ISqlite3> db{ nullptr };

View File

@ -1,7 +1,7 @@
#pragma once
template <typename T>
// This class exists to record the needed to unbind a drop down menu action and delete
// the corresponding item from the drop down menu when the handler is destroyed.
// the corresponding item from the drop down menu when the handler is destroyed.
// (Because the handler is a method of an object that is about to be destroyed.)
// Also avoids the need for manually creating a new windowid to link each additional bind
// to each menu item, thus avoids the likelihood of mismatching binds and menu entries.
@ -91,4 +91,3 @@ public:
};
extern Frame* singletonFrame;

View File

@ -113,7 +113,7 @@ namespace ro {
* and whose members have the spaceship operator
* nonexistent arithemetic values of the shorter array
* are considered zero
* Existent non arithmetic values are considered greater than
* Existent non arithmetic values are considered greater than
* nonexistent non arithemetic values */
template<class T, class U >
std::enable_if_t<!ro::has_spaceship_v<T, U>, decltype(std::span(declval<T>())[0] <=> std::span(declval<U>())[0]) >operator <=>(

@ -1 +1 @@
Subproject commit 561e556dad078af581f338fe3de9ee6362d28b16
Subproject commit 012e892841ed6edc521f88a23b55863c7afe4622

View File

@ -43,7 +43,7 @@ extern const char sz_existing_secret[];
extern const char sz_open_existing_wallet[];
extern const char sz_existing_wallet[];
extern const char sz_text_buffer_overflow[];
extern const char sz_unknown_error[];
extern const char sz_unknown_error[];
extern const char szAboutWallet[];
extern const char szAboutTitle[];

2
mpir

@ -1 +1 @@
Subproject commit 33be9007f95b85230da2330ef3ed525896370cc2
Subproject commit 7e09c025f6061863e58a2cc0a0aefa8b5fa8496b

View File

@ -14,18 +14,54 @@ namespace ro {
return id ^ (id >> 32);
}
class charmap :public std::array<char, 0x100> {
class charmap {
public:
std::array< char, 0x100> index{ 0, };
charmap() = delete;
constexpr charmap(const char* p, const char* q) {
constexpr charmap(const char * p, const char * q) {
while (unsigned int pu{ static_cast<unsigned char>(*p++) }) {
assert((*this)[pu] == 0);
(*this)[pu] = *q++;
assert(index[pu] == 0);
index[pu] = *q++;
}
assert(*(p - 1) == '\0' && *q == '\0');
assert(*(p - 1) == '\0' && *q == '\0');
/* when an instance of this class is declared constexpr,
an assert does not trigger a run time error,
because expression evaluated at compile time.
Instead the compiler reports that the expression
did not evaluate to a constant,
The error is confusing, because the error points to
the declaration where the initialization was invoked,
instead of pointing to the assert.
*/
}
};
class charmapWithFixup {
public:
std::array< char, 0x100> index{ 0, };
charmapWithFixup() = delete;
constexpr charmapWithFixup(const char* p, const char* q) {
while (unsigned int pu{ static_cast<unsigned char>(*p++) }) {
assert(index[pu] == 0);
index[pu] = *q++;
}
index['I'] = index['l']='0';
assert(*(p - 1) == '\0' && *q == '\0');
/* when an instance of this class is declared constexpr,
an assert does not trigger a run time error,
because expression evaluated at compile time.
Instead the compiler reports that the expression
did not evaluate to a constant,
The error is confusing, because the error points to
the declaration where the initialization was invoked,
instead of pointing to the assert.
*/
}
};
//template <> class base58<scalar> : public CompileSizedString<44> {};
static_assert(sizeof(base58<point>) == 46, "base58 point strings unexpected size");
@ -33,17 +69,18 @@ namespace ro {
static constexpr char index2MpirBase58[] { "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuv" };
void map_base_from_mpir58(char* p) {
static const charmap map(index2MpirBase58, index2cryptoBase58);
while (unsigned int pu{ static_cast<unsigned char>(*p) }) {
*p++ = map[pu];
static constexpr charmap map(index2MpirBase58, index2cryptoBase58);
while (uint8_t pu{ static_cast<uint8_t>(*p) }) {
*p++ = map.index[pu];
}
}
void map_base_to_mpir58(const char* p, char* q, size_t count) {
static const charmap map(index2cryptoBase58, index2MpirBase58);
static constexpr charmap map(index2cryptoBase58, index2MpirBase58);
static_assert(map.index[0xF0] == 0);
while (count--) {
unsigned int pu{ static_cast<unsigned char>(*p++) };
char c{ map[pu] };
char c{ map.index[pu] };
if (c == '\0') throw NotBase58Exception();
*q++ = c;
}

View File

@ -143,4 +143,3 @@ template<> CompileSizedString < (sizeof(scalar) * 8 + 5) / 6>
);
return sz;
}

View File

@ -597,7 +597,7 @@ namespace ristretto255 {
// After loading a point as a blog from the network, from the database,
// or from user data typed as text, have to check for validity.
bool valid(void) const { return 0 != crypto_core_ristretto255_is_valid_point(&blob[0]); }
explicit constexpr point(std::array<uint8_t, crypto_core_ristretto255_BYTES>&& in) :
explicit constexpr point(std::array<uint8_t, crypto_core_ristretto255_BYTES>&& in) :
blob{ std::forward<std::array<uint8_t, crypto_core_ristretto255_BYTES>>(in) } { };
static_assert(crypto_core_ristretto255_BYTES == 32, "256 bit points expected");
~point() = default;

View File

@ -15,11 +15,30 @@
static constexpr uint8_t index2base64[]{
"0123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz!$*+-_"
};
/*
control characters are stops
! $ * + - permitted The characters "#%&'(),. are stops
0-9 permitted The characters :;<=>? are stops
A-Z and _ permitted, @ and [\]^ are stops
a-z permitted. {|} ~` are stops, as is the mysterious control character 0x7F (del)
*/
// Unlike regular baseINV, and incompatible with it. Intended to encode stuff small enough
// that it might be human typed and human transmitted, therefore maps 'o' and 'O' to '0', 'I' and
// 'l' to '1'
// uses six url safe additional characters !$*+-_ to bring it up to six bits
//
// But on reflection, useless, since human typed stuff like this should use Bitcoin's base 58 encoding
// So going to switch to regular base64, despite the unreasonably immense amount of work I put into it.
// Unfortunately, Wireguard, with which I am going to need to interoperate, uses RFC4648, whose
// algorithm is fundamentally different - no special treatment for I, O, o, and l, and uses =
// to handle the case where you have boundary problems between eight and six bit groups.
// They force everything to four character groups, using an = sign to indicate that the
// bytes being represented stop before a multiple of three. https://www.base64encode.org
static_assert(index2base64[63] == '_', "surprise numeral at 63");
// Being intended for small bits of data, assumes no whitespace within an entity
@ -35,41 +54,71 @@ static_assert(index2base64[63] == '_', "surprise numeral at 63");
// will return a number of bits less than the size of the input bit buffer, less than the bitcount
// it was given.
// Table to convert ascii to six bit.
static constexpr std::array<constexpr uint8_t, 0x100> ascii2six_ar{
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, //control characters
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, //control characters
0xff, 0x3a, 0xff, 0xff, 0x3b, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3c, 0x3d, 0xff, 0x3e, 0xff, 0xff, // ! $ * + - permitted The characters "#%&'(),. are stops
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // 0-9 permitted The characters :;<=>? are stops
0xff, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x01, 0x12, 0x13, 0x14, 0x15, 0x16, 0x00,
0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0xff, 0xff, 0xff, 0xff, 0x3f, // A-Z and _ permitted, @ and [\]^ are stops
0xff, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x01, 0x2d, 0x2e, 0x00,
0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0xff, 0xff, 0xff, 0xff, 0xff, //a-z the characters {|} ~` are stops, as is the mysterious control character 0x7F (del)
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff // utf8 multibyte characters, all stops.
};
// Table to convert ascii to six bit as a good old fashioned non owning naked pointer to const,
// whose storage is owned by a const static which exists until the program terminates.
static const uint8_t *ascii2six{ &ascii2six_ar[0] };
// Compile time execution is C++ is a pain, because expressions are apt to unpredictably lose
// their constexpr character for reasons that are far from clear.
//
// You can declare anything reasonable to be constexpr, and the compiler will not issue an
// error message until the code that attempts to use what you have declared constexpr is
// invoked from somewhere else "expression does not evaluate to constant"
//
// an assert in an expression evaluated at compile time does not trigger a run time error,
// Instead the compiler reports that the expression did not evaluate to a constant,
//
// The error is confusing, because the error points to the declaration where the initialization
// was invoked,instead of pointing to the assert.
// To debug code intended to be run at compile time, exercise it at run time with
// auto ptr(std::make_unique<Class>());
// at run time;
class charindex {
public:
std::array< uint8_t, 0x100> index{ 0, };
// this non const array will become constexpr and be constructed at compile time
// when an instance of this class is created in a constexpr expression.
charindex() = delete;
constexpr charindex(const uint8_t* p) {
uint8_t pu{ 0 };
do { index[pu++] = 0xFF; } while (pu);
uint8_t i{ 0 };
while (pu = static_cast<uint8_t>(p[i])) {
index[pu] = i;
i++;
assert(i != 0); //prevents unending execution,
// inside a constexp, generates an "expression does not evaluate to constant"
// error at compile time, rather than breaking at run time.
}
index['o'] = index['O'] = 0;
index['l'] = index['I'] = 1;
}
};
static constexpr charindex ascii2six_ar(index2base64);
//
//
// You really have to write compile time code in templates as a language, which is the totally
// obscure and hard to use language apt to generate remarkably voluminous error messages
// will little obvious connection to the actual problem, and surprising result that are ver
// difficult to predict in advance or understand at all.
// In general, the better solution is to have a routine that is called once and only once at the
// beginning of the program, which initializes a bunch of static const values, if that solution is
// adequate, or to have a preproces routine written in python which generates the required C
// files and header files.
// After this experiment in compile time code, I swear off it.
// Table to convert ascii to six bit as a good old fashioned non owning naked pointer to const,
// whose storage is owned by a const static which exists until the program terminates.
const uint8_t* const ascii2six{ &ascii2six_ar.index[0] };
void ascii2test() {
for (unsigned int i{ 0 }; i < 0x100;i++) {
char v = i;
unsigned int j = ascii2six[i];
char w = index2base64[j];
if (j < 64) {
assert(v == w || v == 'I' || v == 'l' || v == 'o' || v == 'O');
}
}
}
// Decode does not have an input span of encoded characters, but a char *, because it assumes
// the string is always terminated by an invalid character, such as the trailing null at the end of

View File

@ -62,7 +62,7 @@ constexpr bool b_WINDOWS = false;
static_assert(wxUSE_UNSAFE_WXSTRING_CONV == 1,
R"(In fully utf environment, (wallet.manifest plus
/utf-8 compile option) all string conversions are safe.)");
static_assert(wxMAJOR_VERSION == 3 && wxMINOR_VERSION == 1 && wxRELEASE_NUMBER == 6 && wxSUBRELEASE_NUMBER == 0, "expecting wxWidgets 3.1.6");
static_assert(wxMAJOR_VERSION == 3 && wxMINOR_VERSION == 1 && wxRELEASE_NUMBER == 7 && wxSUBRELEASE_NUMBER == 0, "expecting wxWidgets 3.1.7");
static_assert(wxUSE_IPV6 == 1, "IP6 unavailable in wxWidgets");
static_assert(WXWIN_COMPATIBILITY_3_0 == 0, "wxWidgets api out of date");
static_assert(wxUSE_COMPILER_TLS == (b_WINDOWS ? 2 : 1), "out of date workarounds in wxWidgets for windows bugs");

View File

@ -21,6 +21,8 @@ Namespace testbed is only defined in this cpp file, hence nothing within
If it needs to interact with the outside world, should post a message
analogously to queue_error_message, which then calls back to a
routine in this file.*/
void ascii2test();
extern const uint8_t* const ascii2six;
namespace testbed {
using ristretto255::hash, ristretto255::hsh, ristretto255::scalar,
@ -42,12 +44,13 @@ No mechanism for input is available. You generally do not need it because you
Uncaught exceptions result in unit test failure, but not in an error
message in the main program UI.
If using a dialog, exceptions within the dialog will result in an error message in the
If using a dialog, exceptions within the dialog will result in an error message in the
main program UI, rather than in the unit test result, since the unit test
is over before the dialog runs.
*/
void testbed() {
// queue_error_message("hello world");
ascii2test();
}
}

@ -1 +1 @@
Subproject commit 35a6d7b15fedfdb5198bb6c28b31cda33b2c2a76
Subproject commit 14c6b431626b817bd7564f4ee3480299307533fa