From a01d043d6aa805b39fdf3108d9a214af2a50dd45 Mon Sep 17 00:00:00 2001 From: guzman-raphael Date: Mon, 18 Nov 2019 13:31:57 -0600 Subject: [PATCH 001/105] Add reference tests. --- +tests/Main.m | 102 ++++++++++++++++++++++++++++++++++++++++ +tests/TestConnection.m | 34 ++++++++++++++ +tests/TestTls.m | 69 +++++++++++++++++++++++++++ .gitignore | 1 - LNX-docker-compose.yml | 43 +++++++++++++++++ 5 files changed, 248 insertions(+), 1 deletion(-) create mode 100644 +tests/Main.m create mode 100644 +tests/TestConnection.m create mode 100644 +tests/TestTls.m create mode 100644 LNX-docker-compose.yml diff --git a/+tests/Main.m b/+tests/Main.m new file mode 100644 index 0000000..e7986e8 --- /dev/null +++ b/+tests/Main.m @@ -0,0 +1,102 @@ +classdef Main < ... + tests.TestConnection & ... + tests.TestTls + + properties (Constant) + CONN_INFO_ROOT = struct(... + 'host', getenv('DJ_HOST'), ... + 'user', getenv('DJ_USER'), ... + 'password', getenv('DJ_PASS')); + CONN_INFO = struct(... + 'host', getenv('DJ_TEST_HOST'), ... + 'user', getenv('DJ_TEST_USER'), ... + 'password', getenv('DJ_TEST_PASSWORD')); + end + + methods (TestClassSetup) + function init(testCase) + disp('---------------INIT---------------'); + clear functions; + testCase.addTeardown(@testCase.dispose); + + curr_conn = dj.conn(testCase.CONN_INFO_ROOT.host, ... + testCase.CONN_INFO_ROOT.user, testCase.CONN_INFO_ROOT.password,'',true); + + ver = curr_conn.query('select @@version as version').version; + if dj.lib.compareVersions(ver,'5.8') + cmd = {... + 'CREATE USER IF NOT EXISTS ''datajoint''@''%%'' ' + 'IDENTIFIED BY ''datajoint'';' + }; + curr_conn.query(sprintf('%s',cmd{:})); + + cmd = {... + 'GRANT ALL PRIVILEGES ON `djtest%%`.* TO ''datajoint''@''%%'';' + }; + curr_conn.query(sprintf('%s',cmd{:})); + + cmd = {... + 'CREATE USER IF NOT EXISTS ''djview''@''%%'' ' + 'IDENTIFIED BY ''djview'';' + }; + curr_conn.query(sprintf('%s',cmd{:})); + + cmd = {... + 'GRANT SELECT ON `djtest%%`.* TO ''djview''@''%%'';' + }; + curr_conn.query(sprintf('%s',cmd{:})); + + cmd = {... + 'CREATE USER IF NOT EXISTS ''djssl''@''%%'' ' + 'IDENTIFIED BY ''djssl'' ' + 'REQUIRE SSL;' + }; + curr_conn.query(sprintf('%s',cmd{:})); + + cmd = {... + 'GRANT SELECT ON `djtest%%`.* TO ''djssl''@''%%'';' + }; + curr_conn.query(sprintf('%s',cmd{:})); + else + cmd = {... + 'GRANT ALL PRIVILEGES ON `djtest%%`.* TO ''datajoint''@''%%'' ' + 'IDENTIFIED BY ''datajoint'';' + }; + curr_conn.query(sprintf('%s',cmd{:})); + + cmd = {... + 'GRANT SELECT ON `djtest%%`.* TO ''djview''@''%%'' ' + 'IDENTIFIED BY ''djview'';' + }; + curr_conn.query(sprintf('%s',cmd{:})); + + cmd = {... + 'GRANT SELECT ON `djtest%%`.* TO ''djssl''@''%%'' ' + 'IDENTIFIED BY ''djssl'' ' + 'REQUIRE SSL;' + }; + curr_conn.query(sprintf('%s',cmd{:})); + end + end + end + + methods (Static) + function dispose() + disp('---------------DISP---------------'); + warning('off','MATLAB:RMDIR:RemovedFromPath'); + + curr_conn = dj.conn(tests.Main.CONN_INFO_ROOT.host, ... + tests.Main.CONN_INFO_ROOT.user, tests.Main.CONN_INFO_ROOT.password, '',true); + + cmd = {... + 'DROP USER ''datajoint''@''%%'';' + 'DROP USER ''djview''@''%%'';' + 'DROP USER ''djssl''@''%%'';' + }; + res = curr_conn.query(sprintf('%s',cmd{:})); + curr_conn.delete; + + warning('on','MATLAB:RMDIR:RemovedFromPath'); + end + end +end diff --git a/+tests/TestConnection.m b/+tests/TestConnection.m new file mode 100644 index 0000000..d57d901 --- /dev/null +++ b/+tests/TestConnection.m @@ -0,0 +1,34 @@ +classdef TestConnection < matlab.unittest.TestCase + % TestConnection tests typical connection scenarios. + methods (Test) + function testConnection(testCase) + st = dbstack; + disp(['---------------' st(1).name '---------------']); + testCase.verifyTrue(dj.conn(... + testCase.CONN_INFO.host,... + testCase.CONN_INFO.user,... + testCase.CONN_INFO.password,'',true).isConnected); + end + function testConnectionExists(testCase) + % testConnectionExists tests that will not fail if connection open + % to the same host. + % Fix https://github.com/datajoint/datajoint-matlab/issues/160 + st = dbstack; + disp(['---------------' st(1).name '---------------']); + dj.conn(testCase.CONN_INFO.host, '', '', '', '', true) + dj.conn(testCase.CONN_INFO.host, '', '', '', '', true) + end + function testConnectionDiffHost(testCase) + % testConnectionDiffHost tests that will fail if connection open + % to a different host. + % Fix https://github.com/datajoint/datajoint-matlab/issues/160 + st = dbstack; + disp(['---------------' st(1).name '---------------']); + dj.conn(testCase.CONN_INFO.host, '', '', '', '', true) + + testCase.verifyError(@() dj.conn(... + 'anything', '', '', '', '', true), ... + 'DataJoint:Connection:AlreadyInstantiated'); + end + end +end \ No newline at end of file diff --git a/+tests/TestTls.m b/+tests/TestTls.m new file mode 100644 index 0000000..9da70f8 --- /dev/null +++ b/+tests/TestTls.m @@ -0,0 +1,69 @@ +classdef TestTls < matlab.unittest.TestCase + % TestTls tests TLS connection scenarios. + methods (Test) + function testSecureConn(testCase) + % secure connection test + st = dbstack; + disp(['---------------' st(1).name '---------------']); + testCase.verifyTrue(length(dj.conn(... + testCase.CONN_INFO.host, ... + testCase.CONN_INFO.user, ... + testCase.CONN_INFO.password, ... + '',true,true).query(... + 'SHOW STATUS LIKE ''Ssl_cipher''').Value{1}) > 0); + end + function testInsecureConn(testCase) + % insecure connection test + st = dbstack; + disp(['---------------' st(1).name '---------------']); + testCase.verifyEqual(dj.conn(... + testCase.CONN_INFO.host, ... + testCase.CONN_INFO.user, ... + testCase.CONN_INFO.password, ... + '',true,false).query(... + 'SHOW STATUS LIKE ''Ssl_cipher''').Value{1}, ... + ''); + end + function testPreferredConn(testCase) + % preferred connection test + st = dbstack; + disp(['---------------' st(1).name '---------------']); + testCase.verifyTrue(length(dj.conn(... + testCase.CONN_INFO.host, ... + testCase.CONN_INFO.user, ... + testCase.CONN_INFO.password, ... + '',true).query(... + 'SHOW STATUS LIKE ''Ssl_cipher''').Value{1}) > 0); + end + function testRejectException(testCase) + % test exception on require TLS + st = dbstack; + disp(['---------------' st(1).name '---------------']); + + try + curr_conn = dj.conn(... + testCase.CONN_INFO.host, ... + 'djssl', ... + 'djssl', ... + '',true,false); + testCase.verifyTrue(false); + catch + e = lasterror; + testCase.verifyEqual(e.identifier, 'MySQL:Error'); + testCase.verifyTrue(contains(e.message,... + ["requires secure connection","Access denied"])); %MySQL8,MySQL5 + end + end + function testStructException(testCase) + % test exception on TLS struct + st = dbstack; + disp(['---------------' st(1).name '---------------']); + testCase.verifyError(@() dj.conn(... + testCase.CONN_INFO.host, ... + testCase.CONN_INFO.user, ... + testCase.CONN_INFO.password, ... + '',true,struct('ca','fake/path/some/where')), ... + 'DataJoint:TLS:InvalidStruct'); + end + end +end \ No newline at end of file diff --git a/.gitignore b/.gitignore index 5bc178c..b4ccfcd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ mym.o build/* -LNX-docker-compose.yml *.env \ No newline at end of file diff --git a/LNX-docker-compose.yml b/LNX-docker-compose.yml new file mode 100644 index 0000000..cbbd212 --- /dev/null +++ b/LNX-docker-compose.yml @@ -0,0 +1,43 @@ +# docker-compose -f LNX-docker-compose.yml --env-file LNX.env up --build --exit-code-from dj +version: '2.4' +x-net: &net + networks: + - main +services: + mym: + <<: *net + environment: + - DISPLAY + - MATLAB_LICENSE + - MATLAB_USER + - DJ_HOST=mysql + - DJ_USER=root + - DJ_PASS=simple + - DJ_TEST_HOST=mysql + - DJ_TEST_USER=datajoint + - DJ_TEST_PASSWORD=datajoint + image: raphaelguzman/matlab:${MATLAB_VERSION}-MIN + depends_on: + mysql: + condition: service_healthy + user: ${MATLAB_UID}:${MATLAB_GID} + working_dir: /src + command: > + /bin/bash -c " + matlab -nodisplay -r \"\ + res=run(tests.Main);\ + disp(res);\ + if all([res.Passed]) exit, else exit(1), end;\ + \"; + " + mac_address: $MATLAB_HOSTID + volumes: + - .:/src + - /tmp/.X11-unix:/tmp/.X11-unix:rw + mysql: + <<: *net + image: datajoint/mysql:${MYSQL_TAG} + environment: + - MYSQL_ROOT_PASSWORD=simple +networks: + main: From 2f32880126d1e6f14a05fd18b3863f8752ec804d Mon Sep 17 00:00:00 2001 From: guzman-raphael Date: Thu, 21 Nov 2019 11:23:12 -0600 Subject: [PATCH 002/105] Add tests + travis. --- +tests/+lib/compareVersions.m | 100 ++++++++++++++++++++ +tests/Main.m | 101 +------------------- +tests/Prep.m | 105 +++++++++++++++++++++ +tests/TestConnection.m | 58 +++++++----- +tests/TestTls.m | 86 +++++++++-------- .gitignore | 3 +- .travis.yml | 47 ++++++++++ LNX-docker-compose.yml | 10 +- mym.m => distribution/mexa64/mym.m | 14 ++- distribution/mexmaci64/mym.m | 138 ++++++++++++++++++++++++++++ distribution/mexw64/mym.m | 138 ++++++++++++++++++++++++++++ local-docker-compose.yml | 71 ++++++++++++++ mex_compilation/compile_linux.m | 1 + mex_compilation/compile_mexmaci64.m | 1 + mex_compilation/compile_windows.m | 1 + src/mym.m | 138 ++++++++++++++++++++++++++++ 16 files changed, 841 insertions(+), 171 deletions(-) create mode 100644 +tests/+lib/compareVersions.m create mode 100644 +tests/Prep.m create mode 100644 .travis.yml rename mym.m => distribution/mexa64/mym.m (90%) create mode 100644 distribution/mexmaci64/mym.m create mode 100644 distribution/mexw64/mym.m create mode 100644 local-docker-compose.yml create mode 100644 src/mym.m diff --git a/+tests/+lib/compareVersions.m b/+tests/+lib/compareVersions.m new file mode 100644 index 0000000..46d7b27 --- /dev/null +++ b/+tests/+lib/compareVersions.m @@ -0,0 +1,100 @@ +function res = compareVersions(verArray, verComp) + % compareVersions - Semantic version comparison (greater than or equal) + % + % This function evaluates if an array of semantic versions is greater than + % or equal to a reference version. + % + % DISTRIBUTION: + % GitHub: https://github.com/guzman-raphael/compareVersions + % FileExchange: https://www.mathworks.com/matlabcentral/fileexchange/71849-compareversions + % + % res = compareVersions(verArray, verComp) + % INPUT: + % verArray: Cell array with the following conditions: + % - be of length >= 1, + % - contain only string elements, and + % - each element must be of length >= 1. + % verComp: String or Char array that verArray will compare against for + % greater than evaluation. Must be: + % - be of length >= 1, and + % - a string. + % OUTPUT: + % res: Logical array that identifies if each cell element in verArray + % is greater than or equal to verComp. + % TESTS: + % Tests included for reference. From root package directory, + % use command: runtests + % + % EXAMPLES: + % output = compareVersions({'3.2.4beta','9.5.2.1','8.0'}, '8.0.0'); %logical([0 1 1]) + % + % NOTES: + % Tests included for reference. From root package directory, + % use command: runtests + % + % Tested: Matlab 9.5.0.944444 (R2018b) Linux + % Author: Raphael Guzman, DataJoint + % + % $License: MIT (use/copy/change/redistribute on own risk) $ + % $File: compareVersions.m $ + % History: + % 001: 2019-06-12 11:00, First version. + % + % OPEN BUGS: + % - None + res_n = length(verArray); + if ~res_n || max(cellfun(@(c) ~ischar(c) && ... + ~isstring(c),verArray)) > 0 || min(cellfun('length',verArray)) == 0 + msg = { + 'compareVersions:Error:CellArray' + 'Cell array to verify must:' + '- be of length >= 1,' + '- contain only string elements, and' + '- each element must be of length >= 1.' + }; + error('compareVersions:Error:CellArray', sprintf('%s\n',msg{:})); + end + if ~ischar(verComp) && ~isstring(verComp) || length(verComp) == 0 + msg = { + 'compareVersions:Error:VersionRef' + 'Version reference must:' + '- be of length >= 1, and' + '- a string.' + }; + error('compareVersions:Error:VersionRef', sprintf('%s\n',msg{:})); + end + res = false(1, res_n); + for i = 1:res_n + shortVer = strsplit(verArray{i}, '.'); + shortVer = cellfun(@(x) str2double(regexp(x,'\d*','Match')), shortVer(1,:)); + longVer = strsplit(verComp, '.'); + longVer = cellfun(@(x) str2double(regexp(x,'\d*','Match')), longVer(1,:)); + shortVer_p = true; + longVer_p = false; + shortVer_s = length(shortVer); + longVer_s = length(longVer); + + if shortVer_s > longVer_s + [longVer shortVer] = deal(shortVer,longVer); + [longVer_s shortVer_s] = deal(shortVer_s,longVer_s); + [longVer_p shortVer_p] = deal(shortVer_p,longVer_p); + end + + shortVer = [shortVer zeros(1,longVer_s - shortVer_s)]; + diff = shortVer - longVer; + match = diff ~= 0; + + if ~match + res(i) = true; + else + pos = 1:longVer_s; + pos = pos(match); + val = diff(pos(1)); + if val > 0 + res(i) = shortVer_p; + elseif val < 0 + res(i) = longVer_p; + end + end + end +end \ No newline at end of file diff --git a/+tests/Main.m b/+tests/Main.m index e7986e8..362be94 100644 --- a/+tests/Main.m +++ b/+tests/Main.m @@ -1,102 +1,5 @@ classdef Main < ... + tests.Prep & ... tests.TestConnection & ... tests.TestTls - - properties (Constant) - CONN_INFO_ROOT = struct(... - 'host', getenv('DJ_HOST'), ... - 'user', getenv('DJ_USER'), ... - 'password', getenv('DJ_PASS')); - CONN_INFO = struct(... - 'host', getenv('DJ_TEST_HOST'), ... - 'user', getenv('DJ_TEST_USER'), ... - 'password', getenv('DJ_TEST_PASSWORD')); - end - - methods (TestClassSetup) - function init(testCase) - disp('---------------INIT---------------'); - clear functions; - testCase.addTeardown(@testCase.dispose); - - curr_conn = dj.conn(testCase.CONN_INFO_ROOT.host, ... - testCase.CONN_INFO_ROOT.user, testCase.CONN_INFO_ROOT.password,'',true); - - ver = curr_conn.query('select @@version as version').version; - if dj.lib.compareVersions(ver,'5.8') - cmd = {... - 'CREATE USER IF NOT EXISTS ''datajoint''@''%%'' ' - 'IDENTIFIED BY ''datajoint'';' - }; - curr_conn.query(sprintf('%s',cmd{:})); - - cmd = {... - 'GRANT ALL PRIVILEGES ON `djtest%%`.* TO ''datajoint''@''%%'';' - }; - curr_conn.query(sprintf('%s',cmd{:})); - - cmd = {... - 'CREATE USER IF NOT EXISTS ''djview''@''%%'' ' - 'IDENTIFIED BY ''djview'';' - }; - curr_conn.query(sprintf('%s',cmd{:})); - - cmd = {... - 'GRANT SELECT ON `djtest%%`.* TO ''djview''@''%%'';' - }; - curr_conn.query(sprintf('%s',cmd{:})); - - cmd = {... - 'CREATE USER IF NOT EXISTS ''djssl''@''%%'' ' - 'IDENTIFIED BY ''djssl'' ' - 'REQUIRE SSL;' - }; - curr_conn.query(sprintf('%s',cmd{:})); - - cmd = {... - 'GRANT SELECT ON `djtest%%`.* TO ''djssl''@''%%'';' - }; - curr_conn.query(sprintf('%s',cmd{:})); - else - cmd = {... - 'GRANT ALL PRIVILEGES ON `djtest%%`.* TO ''datajoint''@''%%'' ' - 'IDENTIFIED BY ''datajoint'';' - }; - curr_conn.query(sprintf('%s',cmd{:})); - - cmd = {... - 'GRANT SELECT ON `djtest%%`.* TO ''djview''@''%%'' ' - 'IDENTIFIED BY ''djview'';' - }; - curr_conn.query(sprintf('%s',cmd{:})); - - cmd = {... - 'GRANT SELECT ON `djtest%%`.* TO ''djssl''@''%%'' ' - 'IDENTIFIED BY ''djssl'' ' - 'REQUIRE SSL;' - }; - curr_conn.query(sprintf('%s',cmd{:})); - end - end - end - - methods (Static) - function dispose() - disp('---------------DISP---------------'); - warning('off','MATLAB:RMDIR:RemovedFromPath'); - - curr_conn = dj.conn(tests.Main.CONN_INFO_ROOT.host, ... - tests.Main.CONN_INFO_ROOT.user, tests.Main.CONN_INFO_ROOT.password, '',true); - - cmd = {... - 'DROP USER ''datajoint''@''%%'';' - 'DROP USER ''djview''@''%%'';' - 'DROP USER ''djssl''@''%%'';' - }; - res = curr_conn.query(sprintf('%s',cmd{:})); - curr_conn.delete; - - warning('on','MATLAB:RMDIR:RemovedFromPath'); - end - end -end +end \ No newline at end of file diff --git a/+tests/Prep.m b/+tests/Prep.m new file mode 100644 index 0000000..fe6f784 --- /dev/null +++ b/+tests/Prep.m @@ -0,0 +1,105 @@ +classdef Prep < matlab.unittest.TestCase + % Setup and teardown for tests. + properties (Constant) + CONN_INFO_ROOT = struct(... + 'host', getenv('DJ_HOST'), ... + 'user', getenv('DJ_USER'), ... + 'password', getenv('DJ_PASS')); + CONN_INFO = struct(... + 'host', getenv('DJ_TEST_HOST'), ... + 'user', getenv('DJ_TEST_USER'), ... + 'password', getenv('DJ_TEST_PASSWORD')); + end + + methods (TestClassSetup) + function init(testCase) + disp('---------------INIT---------------'); + clear functions; + testCase.addTeardown(@testCase.dispose); + addpath('./distribution/mexa64'); + + curr_conn = mym(-1, 'open', testCase.CONN_INFO_ROOT.host, ... + testCase.CONN_INFO_ROOT.user, testCase.CONN_INFO_ROOT.password, ... + 'false'); + + res = mym(curr_conn, 'select @@version as version'); + if tests.lib.compareVersions(res.version,'5.8') + cmd = {... + 'CREATE USER IF NOT EXISTS ''datajoint''@''%%'' ' + 'IDENTIFIED BY ''datajoint'';' + }; + mym(curr_conn, sprintf('%s',cmd{:})); + + cmd = {... + 'GRANT ALL PRIVILEGES ON `djtest%%`.* TO ''datajoint''@''%%'';' + }; + mym(curr_conn, sprintf('%s',cmd{:})); + + cmd = {... + 'CREATE USER IF NOT EXISTS ''djview''@''%%'' ' + 'IDENTIFIED BY ''djview'';' + }; + mym(curr_conn, sprintf('%s',cmd{:})); + + cmd = {... + 'GRANT SELECT ON `djtest%%`.* TO ''djview''@''%%'';' + }; + mym(curr_conn, sprintf('%s',cmd{:})); + + cmd = {... + 'CREATE USER IF NOT EXISTS ''djssl''@''%%'' ' + 'IDENTIFIED BY ''djssl'' ' + 'REQUIRE SSL;' + }; + mym(curr_conn, sprintf('%s',cmd{:})); + + cmd = {... + 'GRANT SELECT ON `djtest%%`.* TO ''djssl''@''%%'';' + }; + mym(curr_conn, sprintf('%s',cmd{:})); + else + cmd = {... + 'GRANT ALL PRIVILEGES ON `djtest%%`.* TO ''datajoint''@''%%'' ' + 'IDENTIFIED BY ''datajoint'';' + }; + mym(curr_conn, sprintf('%s',cmd{:})); + + cmd = {... + 'GRANT SELECT ON `djtest%%`.* TO ''djview''@''%%'' ' + 'IDENTIFIED BY ''djview'';' + }; + mym(curr_conn, sprintf('%s',cmd{:})); + + cmd = {... + 'GRANT SELECT ON `djtest%%`.* TO ''djssl''@''%%'' ' + 'IDENTIFIED BY ''djssl'' ' + 'REQUIRE SSL;' + }; + mym(curr_conn, sprintf('%s',cmd{:})); + end + mym('closeall'); + end + end + + methods (Static) + function dispose() + disp('---------------DISP---------------'); + warning('off','MATLAB:RMDIR:RemovedFromPath'); + + curr_conn = mym(-1, 'open', tests.Prep.CONN_INFO_ROOT.host, ... + tests.Prep.CONN_INFO_ROOT.user, tests.Prep.CONN_INFO_ROOT.password, ... + 'false'); + + cmd = {... + 'DROP USER ''datajoint''@''%%'';' + 'DROP USER ''djview''@''%%'';' + 'DROP USER ''djssl''@''%%'';' + }; + mym(curr_conn, sprintf('%s',cmd{:})); + mym('closeall'); + + warning('on','MATLAB:RMDIR:RemovedFromPath'); + rmpath('./distribution/mexa64') + end + end +end \ No newline at end of file diff --git a/+tests/TestConnection.m b/+tests/TestConnection.m index d57d901..e2d2e87 100644 --- a/+tests/TestConnection.m +++ b/+tests/TestConnection.m @@ -1,34 +1,44 @@ -classdef TestConnection < matlab.unittest.TestCase +classdef TestConnection < tests.Prep % TestConnection tests typical connection scenarios. methods (Test) - function testConnection(testCase) + function testNewConnection(testCase) + % force new connections test st = dbstack; disp(['---------------' st(1).name '---------------']); - testCase.verifyTrue(dj.conn(... - testCase.CONN_INFO.host,... - testCase.CONN_INFO.user,... - testCase.CONN_INFO.password,'',true).isConnected); + curr_conn = mym(-1, 'open', testCase.CONN_INFO.host, ... + testCase.CONN_INFO.user, testCase.CONN_INFO.password, ... + 'false'); + + curr_conn = mym(-1, 'open', testCase.CONN_INFO.host, ... + testCase.CONN_INFO.user, testCase.CONN_INFO.password, ... + 'false'); + + connections = evalc("mym('status')"); + connections = splitlines(connections); + connections(end)=[]; + + testCase.verifyEqual(length(connections), 2); + mym('closeall'); end - function testConnectionExists(testCase) - % testConnectionExists tests that will not fail if connection open - % to the same host. - % Fix https://github.com/datajoint/datajoint-matlab/issues/160 + function testReuseConnection(testCase) + % reuse existing connections test st = dbstack; disp(['---------------' st(1).name '---------------']); - dj.conn(testCase.CONN_INFO.host, '', '', '', '', true) - dj.conn(testCase.CONN_INFO.host, '', '', '', '', true) - end - function testConnectionDiffHost(testCase) - % testConnectionDiffHost tests that will fail if connection open - % to a different host. - % Fix https://github.com/datajoint/datajoint-matlab/issues/160 - st = dbstack; - disp(['---------------' st(1).name '---------------']); - dj.conn(testCase.CONN_INFO.host, '', '', '', '', true) - - testCase.verifyError(@() dj.conn(... - 'anything', '', '', '', '', true), ... - 'DataJoint:Connection:AlreadyInstantiated'); + conn1 = mym('open', testCase.CONN_INFO.host, ... + testCase.CONN_INFO.user, testCase.CONN_INFO.password, ... + 'false'); + + conn2 = mym('open', testCase.CONN_INFO.host, ... + testCase.CONN_INFO.user, testCase.CONN_INFO.password, ... + 'false'); + + connections = evalc("mym('status')"); + connections = splitlines(connections); + connections(end)=[]; + + testCase.verifyEqual(conn1, conn2); + testCase.verifyEqual(length(connections), 1); + mym('closeall'); end end end \ No newline at end of file diff --git a/+tests/TestTls.m b/+tests/TestTls.m index 9da70f8..636d64d 100644 --- a/+tests/TestTls.m +++ b/+tests/TestTls.m @@ -1,69 +1,79 @@ -classdef TestTls < matlab.unittest.TestCase +classdef TestTls < tests.Prep % TestTls tests TLS connection scenarios. methods (Test) function testSecureConn(testCase) % secure connection test st = dbstack; disp(['---------------' st(1).name '---------------']); - testCase.verifyTrue(length(dj.conn(... - testCase.CONN_INFO.host, ... - testCase.CONN_INFO.user, ... - testCase.CONN_INFO.password, ... - '',true,true).query(... - 'SHOW STATUS LIKE ''Ssl_cipher''').Value{1}) > 0); + curr_conn = mym(-1, 'open', testCase.CONN_INFO.host, testCase.CONN_INFO.user, ... + testCase.CONN_INFO.password, 'true'); + + connections = evalc("mym('status')"); + connections = splitlines(connections); + connections(end)=[]; + testCase.verifyTrue(contains(connections{curr_conn+1},'encrypted')); + + res = mym(curr_conn, 'SHOW STATUS LIKE ''Ssl_cipher'';'); + testCase.verifyTrue(length(res.Value{1}) > 0); + mym('closeall'); end function testInsecureConn(testCase) % insecure connection test st = dbstack; disp(['---------------' st(1).name '---------------']); - testCase.verifyEqual(dj.conn(... - testCase.CONN_INFO.host, ... - testCase.CONN_INFO.user, ... - testCase.CONN_INFO.password, ... - '',true,false).query(... - 'SHOW STATUS LIKE ''Ssl_cipher''').Value{1}, ... - ''); + curr_conn = mym(-1, 'open', testCase.CONN_INFO.host, testCase.CONN_INFO.user, ... + testCase.CONN_INFO.password, 'false'); + + connections = evalc("mym('status')"); + connections = splitlines(connections); + connections(end)=[]; + testCase.verifyTrue(~contains(connections{curr_conn+1},'encrypted')); + + res = mym(curr_conn, 'SHOW STATUS LIKE ''Ssl_cipher'';'); + testCase.verifyEqual( ... + res.Value{1}, ... + ''); + mym('closeall'); end function testPreferredConn(testCase) % preferred connection test st = dbstack; disp(['---------------' st(1).name '---------------']); - testCase.verifyTrue(length(dj.conn(... - testCase.CONN_INFO.host, ... - testCase.CONN_INFO.user, ... - testCase.CONN_INFO.password, ... - '',true).query(... - 'SHOW STATUS LIKE ''Ssl_cipher''').Value{1}) > 0); + check(mym(-1, 'open', testCase.CONN_INFO.host, testCase.CONN_INFO.user, ... + testCase.CONN_INFO.password)) + + check(mym(-1, 'open', testCase.CONN_INFO.host, testCase.CONN_INFO.user, ... + testCase.CONN_INFO.password, 'none')) + + check(mym(-1, 'open', testCase.CONN_INFO.host, testCase.CONN_INFO.user, ... + testCase.CONN_INFO.password, 'anything')) + + function check(conn_id) + connections = evalc("mym('status')"); + connections = splitlines(connections); + connections(end)=[]; + testCase.verifyTrue(contains(connections{conn_id+1},'encrypted')); + + res = mym(conn_id, 'SHOW STATUS LIKE ''Ssl_cipher'';'); + testCase.verifyTrue(length(res.Value{1}) > 0); + end + mym('closeall'); end function testRejectException(testCase) % test exception on require TLS st = dbstack; disp(['---------------' st(1).name '---------------']); - try - curr_conn = dj.conn(... - testCase.CONN_INFO.host, ... - 'djssl', ... - 'djssl', ... - '',true,false); + curr_conn = mym(-1, 'open', testCase.CONN_INFO.host, ... + 'djssl', 'djssl', 'false'); testCase.verifyTrue(false); catch e = lasterror; testCase.verifyEqual(e.identifier, 'MySQL:Error'); testCase.verifyTrue(contains(e.message,... ["requires secure connection","Access denied"])); %MySQL8,MySQL5 - end - end - function testStructException(testCase) - % test exception on TLS struct - st = dbstack; - disp(['---------------' st(1).name '---------------']); - testCase.verifyError(@() dj.conn(... - testCase.CONN_INFO.host, ... - testCase.CONN_INFO.user, ... - testCase.CONN_INFO.password, ... - '',true,struct('ca','fake/path/some/where')), ... - 'DataJoint:TLS:InvalidStruct'); + end + mym('closeall'); end end end \ No newline at end of file diff --git a/.gitignore b/.gitignore index b4ccfcd..8f4adbf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ mym.o build/* -*.env \ No newline at end of file +*.env +notebook/* \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..d766b88 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,47 @@ +branches: + only: + - master +sudo: required +services: +- docker +env: + global: + - DOCKER_CLIENT_TIMEOUT: 120 + - COMPOSE_HTTP_TIMEOUT: 120 + - MATLAB_USER: datajoint + - MATLAB_UID: 2000 + - MATLAB_GID: 2000 +slim: &slim + stage: Slim9.9 + os: linux + language: shell + script: + - license=MATLAB_LICENSE_${MATLAB_VERSION} + - export MATLAB_LICENSE=$(eval echo "\$$license") + - docker-compose -f LNX-docker-compose.yml up --exit-code-from dj +jobs: + include: + - <<: *slim + env: + - MATLAB_VERSION: R2018b + - MYSQL_TAG: 8.0 + - <<: *slim + env: + - MATLAB_VERSION: R2018b + - MYSQL_TAG: 5.7 + - <<: *slim + env: + - MATLAB_VERSION: R2018b + - MYSQL_TAG: 5.6 + - <<: *slim + env: + - MATLAB_VERSION: R2019a + - MYSQL_TAG: 8.0 + - <<: *slim + env: + - MATLAB_VERSION: R2019a + - MYSQL_TAG: 5.7 + - <<: *slim + env: + - MATLAB_VERSION: R2019a + - MYSQL_TAG: 5.6 \ No newline at end of file diff --git a/LNX-docker-compose.yml b/LNX-docker-compose.yml index cbbd212..f1e943e 100644 --- a/LNX-docker-compose.yml +++ b/LNX-docker-compose.yml @@ -1,4 +1,4 @@ -# docker-compose -f LNX-docker-compose.yml --env-file LNX.env up --build --exit-code-from dj +# docker-compose -f LNX-docker-compose.yml --env-file LNX.env up --build --exit-code-from mym version: '2.4' x-net: &net networks: @@ -10,15 +10,15 @@ services: - DISPLAY - MATLAB_LICENSE - MATLAB_USER - - DJ_HOST=mysql + - DJ_HOST=db - DJ_USER=root - DJ_PASS=simple - - DJ_TEST_HOST=mysql + - DJ_TEST_HOST=db - DJ_TEST_USER=datajoint - DJ_TEST_PASSWORD=datajoint image: raphaelguzman/matlab:${MATLAB_VERSION}-MIN depends_on: - mysql: + db: condition: service_healthy user: ${MATLAB_UID}:${MATLAB_GID} working_dir: /src @@ -34,7 +34,7 @@ services: volumes: - .:/src - /tmp/.X11-unix:/tmp/.X11-unix:rw - mysql: + db: <<: *net image: datajoint/mysql:${MYSQL_TAG} environment: diff --git a/mym.m b/distribution/mexa64/mym.m similarity index 90% rename from mym.m rename to distribution/mexa64/mym.m index f5e8b66..ef0fac6 100644 --- a/mym.m +++ b/distribution/mexa64/mym.m @@ -15,8 +15,13 @@ % host: default is local host. Use colon for port number % user: default is Unix login name. % password: default says connect without password. -% Examples: mym('open','arkiv') % connect on default port -% mym('open','arkiv:2215') +% use_tls: (optional) TLS encryption type. Values are as follows: +% - 'true': TLS encryption is required. +% - 'false': TLS encryption is disabled. +% - 'nan'(default): TLS encryption utilized if available. +% Examples: mym('open','arkiv') % connect on default port +% mym('open','arkiv:2215') % TLS Preferred +% mym('open','arkiv','root','simple','true') % TLS Required % If successful, open returns 0 if successful, and throw an error % otherwise. The program can maintain up to 20 independent connections. % Any command may be preceded by a connection handle -- an integer from 0 @@ -39,7 +44,8 @@ % Example: mym('use cme') % mym('status') % ------------- -% Display information about the connection and the server. +% Display information about the connection and the server. Connections +% utilizing TLS encryption will be displayed as '(encrypted)'. % Return 0 if connection is open and functioning % 1 if connection is closed % 2 if should be open but we cannot ping the server @@ -122,7 +128,7 @@ % - use std::max(a, b) instead of max(a, b) % v1.0.5 - added the preamble 'u', permitting to save binary fields without using compression % - corrected a bug in mym('closeall') -% - corrected various mistakes in the help file (thanks to Jörg Buchholz) +% - corrected various mistakes in the help file (thanks to J�rg Buchholz) % v1.0.4 corrected the behaviour of mYm with time fields, now return a string dump of the field % v1.0.3 minor corrections % v1.0.2 put mYm under GPL license, official release diff --git a/distribution/mexmaci64/mym.m b/distribution/mexmaci64/mym.m new file mode 100644 index 0000000..ef0fac6 --- /dev/null +++ b/distribution/mexmaci64/mym.m @@ -0,0 +1,138 @@ +% MYM - Interact with a MySQL database server +% Copyright 2005, EPFL (Yannick Maret) +% +% Copyright notice: this code is a heavily modified version of the original +% work of Robert Almgren from University of Toronto. +% +% If no output arguments are given, then display results. Otherwise returns +% requested data silently. +% mym() or mym +% ------------ +% shows status of all open connections (returns nothing). +% mym('open', host, user, password) +% --------------------------------- +% Open a connection with specified parameters, or defaults if none +% host: default is local host. Use colon for port number +% user: default is Unix login name. +% password: default says connect without password. +% use_tls: (optional) TLS encryption type. Values are as follows: +% - 'true': TLS encryption is required. +% - 'false': TLS encryption is disabled. +% - 'nan'(default): TLS encryption utilized if available. +% Examples: mym('open','arkiv') % connect on default port +% mym('open','arkiv:2215') % TLS Preferred +% mym('open','arkiv','root','simple','true') % TLS Required +% If successful, open returns 0 if successful, and throw an error +% otherwise. The program can maintain up to 20 independent connections. +% Any command may be preceded by a connection handle -- an integer from 0 +% to 10 -- to apply the command to that connection. +% Examples: mym(5,'open','host2') % connection 5 to host 2 +% mym % status of all connections +% When no connection handle is given, mym use 0 by default. If the +% corresponding connection is open, it is closed and opened again. +% It is possible to ask mym to look for an available connection +% handle by using -1. The used connection handle is then returned. +% Example: cid = mym(-1, 'open', 'host2') % cid contains the used +% connection handle +% mym('close') +% ------------ +% Close the current connection. Use mym('closeall') to closes all open +% connections. +% mym('use',db) or mym('use db') +% --------------------------------- +% Set the current database to db +% Example: mym('use cme') +% mym('status') +% ------------- +% Display information about the connection and the server. Connections +% utilizing TLS encryption will be displayed as '(encrypted)'. +% Return 0 if connection is open and functioning +% 1 if connection is closed +% 2 if should be open but we cannot ping the server +% mym(query) +% ---------- +% Send the given query or command to the MySQL server. If arguments are +% given on the left, then each argument is set to the column of the +% returned query. Dates and times in result are converted to Matlab +% format: dates are serial day number, and times are fraction of day. +% String variables are returned as cell arrays. +% Example: p = mym('select price from contract where date="1997-04-30"'); +% % Returns price for contract that occured on April 30, 1997. +% Note: All string comparisons are case-insensitive +% Placeholders: in a query the following placeholders can be used: {S}, +% {Si}, {M}, {F}, and {B}. +% Example: i = 1000; +% B = [1 2; 3 4]; +% mym('INSERT INTO test(id,value) VALUES("{Si}","{M}")',i,B); +% A = mym('SELECT value FROM test WHERE id ="{Si}")', 1000); +% % Insert the array B into table test with id=1000. Then the +% % value is retrieved and put into A. +% {S} is remplaced by a string given by the corresponding argument arg. +% Arg can be a matlab string or a scalar. The format of the string +% for double scalars is [sign]d.ddddddEddd; for integers the format +% is [sign]dddddd. +% {Sn} is the same as {S} but for double scalar only. The format of the +% string is [sign]d.ddddEddd, where the number of decimal after the +% dot is given by n. +% {Si} is the same as {S} but for double scalar only. The corresponding +% double is first converted to an integer (using floor). +% {M} is replaced by the binary representation of the corresponding +% argument (it can be a scalar, cell, numeric or cell array, or a +% structure). +% {B} is replaced by the binary representation of the uint8 vector +% given in the corresponding argument. +% {F} is the same as {B} but for the file whose name is given in the +% corresponding argument. +% Note: 1) {M}, {B} and {F} need to be put in fields of type BLOB +% 2) {M}, {B} and {F} binary representations are compressed -- only +% if the space gain is larger than 10% --. We use zlib v1.2.3! +% The compression can be switched off by using {uM}, {uB} and +% {uF}, instead. +% 3) {M} does not work if the endian of the client used to store +% the BLOB is different than that used to fetch it. +% 4) time fields are returned as string dump +% 5) this kind of insert does not work properly: +% mym(INSERT INTO tbl(id,txt) VALUES(1000,"abc{dfg}h")'); +% as the "abc{dfg}h" is mistaken for a mYm command. A possible +% solution is to use the following command: +% mym(INSERT INTO tbl(id,txt) VALUES(1000,"{S}")','abc{dfg}h'); +% +% mym('version') +% -------------- +% Displays the version of mym when no output arguments are given. +% With a single output argument, a struct with the fields 'major', +% 'minor' and 'bugfix' is returned. These fields contain numeric +% scalars corresponding to the version major.minor.bugfix +% +% HISTORY +% ------- +% v1.36 - fixed bug for Linux64 related to cross-platform compatibility. Blobs written on windows can be read under +% Linux 32/64 and vice-versa. +% v1.35 - fixed bug with incorrect pointer increment under Linux +% v1.3 - fixed bug with saving corrupted cells +% v1.2 - fixed bug with memory allocation, result now is returned as a structure +% v1.1 - fixed bug with port number being ignored when specified +% +% v1.0.9 - a space is now used when the variable corresponding to a string placeholder is empty +% - we now use strtod instead of sscanf(s, "%lf") +% - add support for stored procedure +% v1.0.8 - corrected a problem occurring with MySQL commands that do not return results +% - the M$ Windows binary now use the correct runtime DLL (MSVC80.DLL insteaLd of MSVC80D.DL) +% v1.0.7 - logical values are now correctly considered as numerical value when using placeholder {Si} +% - corrected a bug occuring when closing a connection that was not openned +% - added the possibility to get the next free connection ID when oppening a connection +% v1.0.6 - corrected a bug where mym('use', 'a_schema') worked fine while mym(conn, 'use', 'a_schema') did not work +% - corrected a segmentation violation that happened when issuing a MySQL command when not connected +% - corrected the mex command (this file) +% - corrected a bug where it was impossible to open a connection silently +% - use std::max(a, b) instead of max(a, b) +% v1.0.5 - added the preamble 'u', permitting to save binary fields without using compression +% - corrected a bug in mym('closeall') +% - corrected various mistakes in the help file (thanks to J�rg Buchholz) +% v1.0.4 corrected the behaviour of mYm with time fields, now return a string dump of the field +% v1.0.3 minor corrections +% v1.0.2 put mYm under GPL license, official release +% v1.0.1 corrected a bug where non-matlab binary objects were not returned +% v1.0.0 initial release + + diff --git a/distribution/mexw64/mym.m b/distribution/mexw64/mym.m new file mode 100644 index 0000000..ef0fac6 --- /dev/null +++ b/distribution/mexw64/mym.m @@ -0,0 +1,138 @@ +% MYM - Interact with a MySQL database server +% Copyright 2005, EPFL (Yannick Maret) +% +% Copyright notice: this code is a heavily modified version of the original +% work of Robert Almgren from University of Toronto. +% +% If no output arguments are given, then display results. Otherwise returns +% requested data silently. +% mym() or mym +% ------------ +% shows status of all open connections (returns nothing). +% mym('open', host, user, password) +% --------------------------------- +% Open a connection with specified parameters, or defaults if none +% host: default is local host. Use colon for port number +% user: default is Unix login name. +% password: default says connect without password. +% use_tls: (optional) TLS encryption type. Values are as follows: +% - 'true': TLS encryption is required. +% - 'false': TLS encryption is disabled. +% - 'nan'(default): TLS encryption utilized if available. +% Examples: mym('open','arkiv') % connect on default port +% mym('open','arkiv:2215') % TLS Preferred +% mym('open','arkiv','root','simple','true') % TLS Required +% If successful, open returns 0 if successful, and throw an error +% otherwise. The program can maintain up to 20 independent connections. +% Any command may be preceded by a connection handle -- an integer from 0 +% to 10 -- to apply the command to that connection. +% Examples: mym(5,'open','host2') % connection 5 to host 2 +% mym % status of all connections +% When no connection handle is given, mym use 0 by default. If the +% corresponding connection is open, it is closed and opened again. +% It is possible to ask mym to look for an available connection +% handle by using -1. The used connection handle is then returned. +% Example: cid = mym(-1, 'open', 'host2') % cid contains the used +% connection handle +% mym('close') +% ------------ +% Close the current connection. Use mym('closeall') to closes all open +% connections. +% mym('use',db) or mym('use db') +% --------------------------------- +% Set the current database to db +% Example: mym('use cme') +% mym('status') +% ------------- +% Display information about the connection and the server. Connections +% utilizing TLS encryption will be displayed as '(encrypted)'. +% Return 0 if connection is open and functioning +% 1 if connection is closed +% 2 if should be open but we cannot ping the server +% mym(query) +% ---------- +% Send the given query or command to the MySQL server. If arguments are +% given on the left, then each argument is set to the column of the +% returned query. Dates and times in result are converted to Matlab +% format: dates are serial day number, and times are fraction of day. +% String variables are returned as cell arrays. +% Example: p = mym('select price from contract where date="1997-04-30"'); +% % Returns price for contract that occured on April 30, 1997. +% Note: All string comparisons are case-insensitive +% Placeholders: in a query the following placeholders can be used: {S}, +% {Si}, {M}, {F}, and {B}. +% Example: i = 1000; +% B = [1 2; 3 4]; +% mym('INSERT INTO test(id,value) VALUES("{Si}","{M}")',i,B); +% A = mym('SELECT value FROM test WHERE id ="{Si}")', 1000); +% % Insert the array B into table test with id=1000. Then the +% % value is retrieved and put into A. +% {S} is remplaced by a string given by the corresponding argument arg. +% Arg can be a matlab string or a scalar. The format of the string +% for double scalars is [sign]d.ddddddEddd; for integers the format +% is [sign]dddddd. +% {Sn} is the same as {S} but for double scalar only. The format of the +% string is [sign]d.ddddEddd, where the number of decimal after the +% dot is given by n. +% {Si} is the same as {S} but for double scalar only. The corresponding +% double is first converted to an integer (using floor). +% {M} is replaced by the binary representation of the corresponding +% argument (it can be a scalar, cell, numeric or cell array, or a +% structure). +% {B} is replaced by the binary representation of the uint8 vector +% given in the corresponding argument. +% {F} is the same as {B} but for the file whose name is given in the +% corresponding argument. +% Note: 1) {M}, {B} and {F} need to be put in fields of type BLOB +% 2) {M}, {B} and {F} binary representations are compressed -- only +% if the space gain is larger than 10% --. We use zlib v1.2.3! +% The compression can be switched off by using {uM}, {uB} and +% {uF}, instead. +% 3) {M} does not work if the endian of the client used to store +% the BLOB is different than that used to fetch it. +% 4) time fields are returned as string dump +% 5) this kind of insert does not work properly: +% mym(INSERT INTO tbl(id,txt) VALUES(1000,"abc{dfg}h")'); +% as the "abc{dfg}h" is mistaken for a mYm command. A possible +% solution is to use the following command: +% mym(INSERT INTO tbl(id,txt) VALUES(1000,"{S}")','abc{dfg}h'); +% +% mym('version') +% -------------- +% Displays the version of mym when no output arguments are given. +% With a single output argument, a struct with the fields 'major', +% 'minor' and 'bugfix' is returned. These fields contain numeric +% scalars corresponding to the version major.minor.bugfix +% +% HISTORY +% ------- +% v1.36 - fixed bug for Linux64 related to cross-platform compatibility. Blobs written on windows can be read under +% Linux 32/64 and vice-versa. +% v1.35 - fixed bug with incorrect pointer increment under Linux +% v1.3 - fixed bug with saving corrupted cells +% v1.2 - fixed bug with memory allocation, result now is returned as a structure +% v1.1 - fixed bug with port number being ignored when specified +% +% v1.0.9 - a space is now used when the variable corresponding to a string placeholder is empty +% - we now use strtod instead of sscanf(s, "%lf") +% - add support for stored procedure +% v1.0.8 - corrected a problem occurring with MySQL commands that do not return results +% - the M$ Windows binary now use the correct runtime DLL (MSVC80.DLL insteaLd of MSVC80D.DL) +% v1.0.7 - logical values are now correctly considered as numerical value when using placeholder {Si} +% - corrected a bug occuring when closing a connection that was not openned +% - added the possibility to get the next free connection ID when oppening a connection +% v1.0.6 - corrected a bug where mym('use', 'a_schema') worked fine while mym(conn, 'use', 'a_schema') did not work +% - corrected a segmentation violation that happened when issuing a MySQL command when not connected +% - corrected the mex command (this file) +% - corrected a bug where it was impossible to open a connection silently +% - use std::max(a, b) instead of max(a, b) +% v1.0.5 - added the preamble 'u', permitting to save binary fields without using compression +% - corrected a bug in mym('closeall') +% - corrected various mistakes in the help file (thanks to J�rg Buchholz) +% v1.0.4 corrected the behaviour of mYm with time fields, now return a string dump of the field +% v1.0.3 minor corrections +% v1.0.2 put mYm under GPL license, official release +% v1.0.1 corrected a bug where non-matlab binary objects were not returned +% v1.0.0 initial release + + diff --git a/local-docker-compose.yml b/local-docker-compose.yml new file mode 100644 index 0000000..b62306b --- /dev/null +++ b/local-docker-compose.yml @@ -0,0 +1,71 @@ +# docker-compose -f local-docker-compose.yml up +version: '2.4' +x-net: &net + networks: + - main +services: + mym: + <<: *net + environment: + - DJ_HOST=db + - DJ_USER=root + - DJ_PASS=simple + - DJ_TEST_HOST=db + - DJ_TEST_USER=datajoint + - DJ_TEST_PASSWORD=datajoint + - MATLAB_USER + - MATLAB_LICENSE + - JUPYTER_PASSWORD=datajoint + - DISPLAY + image: raphaelguzman/matlab:${MATLAB_VERSION}-GUI + depends_on: + db: + condition: service_healthy + ports: + - "8888:8888" + user: ${MATLAB_UID}:${MATLAB_GID} + working_dir: /src + command: > + /bin/bash -c " + ## Set mym path + matlab -nodisplay -r \"\ + addpath('./distribution/mexa64');\ + savepath;\ + \"; + ## Interactive Jupyter Notebook environment + jupyter notebook; + ## Run tests manually + ### all (in MATLAB or Jupyter Notebook) + # run(tests.Main) + ### single (in MATLAB or Jupyter Notebook) + # runtests('tests.TestTls/testInsecureConn') + ### group (in MATLAB or Jupyter Notebook) + # import matlab.unittest.TestSuite; + # import matlab.unittest.selectors.HasName; + # import matlab.unittest.constraints.ContainsSubstring; + # suite = TestSuite.fromClass(?tests.Main, HasName(ContainsSubstring('Conn'))); + # run(suite) + ### class (in MATLAB or Jupyter Notebook) + # run(tests.TestTls) + ## Remote debugger (Matlab GUI) + # matlab; + " + mac_address: $MATLAB_HOSTID + volumes: + ## Dev mounts + - .:/src + - /tmp/.X11-unix:/tmp/.X11-unix:rw + ## Additional mounts may go here + - ./notebook:/home/muser/notebooks + db: + <<: *net + image: datajoint/mysql:${MYSQL_TAG} + environment: + - MYSQL_ROOT_PASSWORD=simple + ports: + - "3306:3306" + ## To persist MySQL data + # volumes: + # - ./mysql/data:/var/lib/mysql +networks: + main: diff --git a/mex_compilation/compile_linux.m b/mex_compilation/compile_linux.m index 90f0f07..41605b5 100644 --- a/mex_compilation/compile_linux.m +++ b/mex_compilation/compile_linux.m @@ -35,6 +35,7 @@ function compile_linux() % Pack mex with all dependencies into distribution directory copyfile(['mym.' mexext()], distrib_out); +copyfile(fullfile(mym_src, 'mym.m'), distrib_out); copyfile(fullfile(lib, '*'), distrib_out); copyfile(fullfile(mysql_lib, 'libmysqlclient.so*'), distrib_out); copyfile(fullfile(mariadb_lib, 'dialog.so'), distrib_out); \ No newline at end of file diff --git a/mex_compilation/compile_mexmaci64.m b/mex_compilation/compile_mexmaci64.m index 2bb6754..6976d16 100755 --- a/mex_compilation/compile_mexmaci64.m +++ b/mex_compilation/compile_mexmaci64.m @@ -44,5 +44,6 @@ function compile_mexmaci64() % Pack mex with all dependencies into distribution directory copyfile(['mym.' mexext()], distrib_out, 'f'); +copyfile(fullfile(mym_src, 'mym.m'), distrib_out, '-f'); copyfile(fullfile(mysql_lib, 'libmysqlclient*'), distrib_out, 'f'); copyfile(fullfile(mariadb_lib, 'dialog.so'), distrib_out, 'f'); \ No newline at end of file diff --git a/mex_compilation/compile_windows.m b/mex_compilation/compile_windows.m index 8cebafe..7b553ec 100644 --- a/mex_compilation/compile_windows.m +++ b/mex_compilation/compile_windows.m @@ -38,5 +38,6 @@ function compile_windows() % Pack mex with all dependencies into distribution directory copyfile(['mym.' mexext()], distrib_out); +copyfile(fullfile(mym_src, 'mym.m'), distrib_out); copyfile(fullfile(mysql_lib, '*.dll'), distrib_out); copyfile(fullfile(mariadb_lib, 'dialog.dll'), distrib_out); diff --git a/src/mym.m b/src/mym.m new file mode 100644 index 0000000..ef0fac6 --- /dev/null +++ b/src/mym.m @@ -0,0 +1,138 @@ +% MYM - Interact with a MySQL database server +% Copyright 2005, EPFL (Yannick Maret) +% +% Copyright notice: this code is a heavily modified version of the original +% work of Robert Almgren from University of Toronto. +% +% If no output arguments are given, then display results. Otherwise returns +% requested data silently. +% mym() or mym +% ------------ +% shows status of all open connections (returns nothing). +% mym('open', host, user, password) +% --------------------------------- +% Open a connection with specified parameters, or defaults if none +% host: default is local host. Use colon for port number +% user: default is Unix login name. +% password: default says connect without password. +% use_tls: (optional) TLS encryption type. Values are as follows: +% - 'true': TLS encryption is required. +% - 'false': TLS encryption is disabled. +% - 'nan'(default): TLS encryption utilized if available. +% Examples: mym('open','arkiv') % connect on default port +% mym('open','arkiv:2215') % TLS Preferred +% mym('open','arkiv','root','simple','true') % TLS Required +% If successful, open returns 0 if successful, and throw an error +% otherwise. The program can maintain up to 20 independent connections. +% Any command may be preceded by a connection handle -- an integer from 0 +% to 10 -- to apply the command to that connection. +% Examples: mym(5,'open','host2') % connection 5 to host 2 +% mym % status of all connections +% When no connection handle is given, mym use 0 by default. If the +% corresponding connection is open, it is closed and opened again. +% It is possible to ask mym to look for an available connection +% handle by using -1. The used connection handle is then returned. +% Example: cid = mym(-1, 'open', 'host2') % cid contains the used +% connection handle +% mym('close') +% ------------ +% Close the current connection. Use mym('closeall') to closes all open +% connections. +% mym('use',db) or mym('use db') +% --------------------------------- +% Set the current database to db +% Example: mym('use cme') +% mym('status') +% ------------- +% Display information about the connection and the server. Connections +% utilizing TLS encryption will be displayed as '(encrypted)'. +% Return 0 if connection is open and functioning +% 1 if connection is closed +% 2 if should be open but we cannot ping the server +% mym(query) +% ---------- +% Send the given query or command to the MySQL server. If arguments are +% given on the left, then each argument is set to the column of the +% returned query. Dates and times in result are converted to Matlab +% format: dates are serial day number, and times are fraction of day. +% String variables are returned as cell arrays. +% Example: p = mym('select price from contract where date="1997-04-30"'); +% % Returns price for contract that occured on April 30, 1997. +% Note: All string comparisons are case-insensitive +% Placeholders: in a query the following placeholders can be used: {S}, +% {Si}, {M}, {F}, and {B}. +% Example: i = 1000; +% B = [1 2; 3 4]; +% mym('INSERT INTO test(id,value) VALUES("{Si}","{M}")',i,B); +% A = mym('SELECT value FROM test WHERE id ="{Si}")', 1000); +% % Insert the array B into table test with id=1000. Then the +% % value is retrieved and put into A. +% {S} is remplaced by a string given by the corresponding argument arg. +% Arg can be a matlab string or a scalar. The format of the string +% for double scalars is [sign]d.ddddddEddd; for integers the format +% is [sign]dddddd. +% {Sn} is the same as {S} but for double scalar only. The format of the +% string is [sign]d.ddddEddd, where the number of decimal after the +% dot is given by n. +% {Si} is the same as {S} but for double scalar only. The corresponding +% double is first converted to an integer (using floor). +% {M} is replaced by the binary representation of the corresponding +% argument (it can be a scalar, cell, numeric or cell array, or a +% structure). +% {B} is replaced by the binary representation of the uint8 vector +% given in the corresponding argument. +% {F} is the same as {B} but for the file whose name is given in the +% corresponding argument. +% Note: 1) {M}, {B} and {F} need to be put in fields of type BLOB +% 2) {M}, {B} and {F} binary representations are compressed -- only +% if the space gain is larger than 10% --. We use zlib v1.2.3! +% The compression can be switched off by using {uM}, {uB} and +% {uF}, instead. +% 3) {M} does not work if the endian of the client used to store +% the BLOB is different than that used to fetch it. +% 4) time fields are returned as string dump +% 5) this kind of insert does not work properly: +% mym(INSERT INTO tbl(id,txt) VALUES(1000,"abc{dfg}h")'); +% as the "abc{dfg}h" is mistaken for a mYm command. A possible +% solution is to use the following command: +% mym(INSERT INTO tbl(id,txt) VALUES(1000,"{S}")','abc{dfg}h'); +% +% mym('version') +% -------------- +% Displays the version of mym when no output arguments are given. +% With a single output argument, a struct with the fields 'major', +% 'minor' and 'bugfix' is returned. These fields contain numeric +% scalars corresponding to the version major.minor.bugfix +% +% HISTORY +% ------- +% v1.36 - fixed bug for Linux64 related to cross-platform compatibility. Blobs written on windows can be read under +% Linux 32/64 and vice-versa. +% v1.35 - fixed bug with incorrect pointer increment under Linux +% v1.3 - fixed bug with saving corrupted cells +% v1.2 - fixed bug with memory allocation, result now is returned as a structure +% v1.1 - fixed bug with port number being ignored when specified +% +% v1.0.9 - a space is now used when the variable corresponding to a string placeholder is empty +% - we now use strtod instead of sscanf(s, "%lf") +% - add support for stored procedure +% v1.0.8 - corrected a problem occurring with MySQL commands that do not return results +% - the M$ Windows binary now use the correct runtime DLL (MSVC80.DLL insteaLd of MSVC80D.DL) +% v1.0.7 - logical values are now correctly considered as numerical value when using placeholder {Si} +% - corrected a bug occuring when closing a connection that was not openned +% - added the possibility to get the next free connection ID when oppening a connection +% v1.0.6 - corrected a bug where mym('use', 'a_schema') worked fine while mym(conn, 'use', 'a_schema') did not work +% - corrected a segmentation violation that happened when issuing a MySQL command when not connected +% - corrected the mex command (this file) +% - corrected a bug where it was impossible to open a connection silently +% - use std::max(a, b) instead of max(a, b) +% v1.0.5 - added the preamble 'u', permitting to save binary fields without using compression +% - corrected a bug in mym('closeall') +% - corrected various mistakes in the help file (thanks to J�rg Buchholz) +% v1.0.4 corrected the behaviour of mYm with time fields, now return a string dump of the field +% v1.0.3 minor corrections +% v1.0.2 put mYm under GPL license, official release +% v1.0.1 corrected a bug where non-matlab binary objects were not returned +% v1.0.0 initial release + + From cebfbea7ba603f33cabcbd938c4efcd01aba2558 Mon Sep 17 00:00:00 2001 From: guzman-raphael Date: Thu, 21 Nov 2019 12:08:14 -0600 Subject: [PATCH 003/105] Update docs. --- README.md | 52 ++++++++++++++++++++++++++++++++++++ distribution/mexa64/mym.m | 2 +- distribution/mexmaci64/mym.m | 2 +- distribution/mexw64/mym.m | 2 +- local-docker-compose.yml | 19 ++----------- src/mym.m | 2 +- 6 files changed, 58 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 6a2da87..9548250 100644 --- a/README.md +++ b/README.md @@ -10,3 +10,55 @@ Copyright notice: this code is a heavily modified version of the original work of Robert Almgren from University of Toronto [sourceforge project](http://sourceforge.net/projects/mym/) See mym.m for further documentation. + + +Running Tests Locally +===================== + + +* Create an `.env` with desired development environment values e.g. +``` sh +MATLAB_USER=raphael +MATLAB_LICENSE="#\ BEGIN----...---------END" # See [guzman-raphael/matlab](https://github.com/guzman-raphael/matlab) for detailed instructions on image use hosted [DockerHub](https://hub.docker.com/r/raphaelguzman/matlab) +MATLAB_VERSION=R2018b +MATLAB_HOSTID=XX:XX:XX:XX:XX:XX +MATLAB_UID=1000 +MATLAB_GID=1000 +MYSQL_TAG=5.7 +``` +* `cp local-docker-compose.yml docker-compose.yml` +* `docker-compose up` (Note configured `JUPYTER_PASSWORD`) +* Select a means of running MATLAB e.g. Jupyter Notebook, GUI, or Terminal (see bottom) +* Run desired tests. Some examples are as follows: + +| Use Case | MATLAB Code | +| ---------------------------- | ------------------------------------------------------------------------------ | +| Run all tests | `run(tests.Main)` | +| Run one class of tests | `run(tests.TestTls)` | +| Run one specific test | `runtests('tests.TestTls/testInsecureConn')` | +| Run tests based on test name | `import matlab.unittest.TestSuite;` | +| | `import matlab.unittest.selectors.HasName;` | +| | `import matlab.unittest.constraints.ContainsSubstring;` | +| | `suite = TestSuite.fromClass(?tests.Main, HasName(ContainsSubstring('Conn')));`| +| | `run(suite)` | + + +Launch Jupyter Notebook +----------------------- +* Navigate to `localhost:8888` +* Input Jupyter password +* Launch a notebook i.e. `New > MATLAB` + + +Launch MATLAB GUI (supports remote interactive debugger) +-------------------------------------------------------- +* Shell into `mym_app_1` i.e. `docker exec -it mym_app_1 bash` +* Launch Matlab by runnning command `matlab` + + +Launch MATLAB Terminal +---------------------- +* Shell into `mym_app_1` i.e. `docker exec -it mym_app_1 bash` +* Launch Matlab with no GUI by runnning command `matlab -nodisplay` + + diff --git a/distribution/mexa64/mym.m b/distribution/mexa64/mym.m index ef0fac6..f3ed566 100644 --- a/distribution/mexa64/mym.m +++ b/distribution/mexa64/mym.m @@ -128,7 +128,7 @@ % - use std::max(a, b) instead of max(a, b) % v1.0.5 - added the preamble 'u', permitting to save binary fields without using compression % - corrected a bug in mym('closeall') -% - corrected various mistakes in the help file (thanks to J�rg Buchholz) +% - corrected various mistakes in the help file (thanks to Jörg Buchholz) % v1.0.4 corrected the behaviour of mYm with time fields, now return a string dump of the field % v1.0.3 minor corrections % v1.0.2 put mYm under GPL license, official release diff --git a/distribution/mexmaci64/mym.m b/distribution/mexmaci64/mym.m index ef0fac6..f3ed566 100644 --- a/distribution/mexmaci64/mym.m +++ b/distribution/mexmaci64/mym.m @@ -128,7 +128,7 @@ % - use std::max(a, b) instead of max(a, b) % v1.0.5 - added the preamble 'u', permitting to save binary fields without using compression % - corrected a bug in mym('closeall') -% - corrected various mistakes in the help file (thanks to J�rg Buchholz) +% - corrected various mistakes in the help file (thanks to Jörg Buchholz) % v1.0.4 corrected the behaviour of mYm with time fields, now return a string dump of the field % v1.0.3 minor corrections % v1.0.2 put mYm under GPL license, official release diff --git a/distribution/mexw64/mym.m b/distribution/mexw64/mym.m index ef0fac6..f3ed566 100644 --- a/distribution/mexw64/mym.m +++ b/distribution/mexw64/mym.m @@ -128,7 +128,7 @@ % - use std::max(a, b) instead of max(a, b) % v1.0.5 - added the preamble 'u', permitting to save binary fields without using compression % - corrected a bug in mym('closeall') -% - corrected various mistakes in the help file (thanks to J�rg Buchholz) +% - corrected various mistakes in the help file (thanks to Jörg Buchholz) % v1.0.4 corrected the behaviour of mYm with time fields, now return a string dump of the field % v1.0.3 minor corrections % v1.0.2 put mYm under GPL license, official release diff --git a/local-docker-compose.yml b/local-docker-compose.yml index b62306b..8e3842e 100644 --- a/local-docker-compose.yml +++ b/local-docker-compose.yml @@ -4,7 +4,7 @@ x-net: &net networks: - main services: - mym: + app: <<: *net environment: - DJ_HOST=db @@ -34,21 +34,6 @@ services: \"; ## Interactive Jupyter Notebook environment jupyter notebook; - ## Run tests manually - ### all (in MATLAB or Jupyter Notebook) - # run(tests.Main) - ### single (in MATLAB or Jupyter Notebook) - # runtests('tests.TestTls/testInsecureConn') - ### group (in MATLAB or Jupyter Notebook) - # import matlab.unittest.TestSuite; - # import matlab.unittest.selectors.HasName; - # import matlab.unittest.constraints.ContainsSubstring; - # suite = TestSuite.fromClass(?tests.Main, HasName(ContainsSubstring('Conn'))); - # run(suite) - ### class (in MATLAB or Jupyter Notebook) - # run(tests.TestTls) - ## Remote debugger (Matlab GUI) - # matlab; " mac_address: $MATLAB_HOSTID volumes: @@ -56,7 +41,7 @@ services: - .:/src - /tmp/.X11-unix:/tmp/.X11-unix:rw ## Additional mounts may go here - - ./notebook:/home/muser/notebooks + # - ./notebook:/home/muser/notebooks db: <<: *net image: datajoint/mysql:${MYSQL_TAG} diff --git a/src/mym.m b/src/mym.m index ef0fac6..f3ed566 100644 --- a/src/mym.m +++ b/src/mym.m @@ -128,7 +128,7 @@ % - use std::max(a, b) instead of max(a, b) % v1.0.5 - added the preamble 'u', permitting to save binary fields without using compression % - corrected a bug in mym('closeall') -% - corrected various mistakes in the help file (thanks to J�rg Buchholz) +% - corrected various mistakes in the help file (thanks to Jörg Buchholz) % v1.0.4 corrected the behaviour of mYm with time fields, now return a string dump of the field % v1.0.3 minor corrections % v1.0.2 put mYm under GPL license, official release From 03f75587d4eb9883256c1003ae1b65dde53aac9d Mon Sep 17 00:00:00 2001 From: guzman-raphael Date: Thu, 21 Nov 2019 12:15:35 -0600 Subject: [PATCH 004/105] Fix styling. --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9548250..851444a 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Running Tests Locally * Create an `.env` with desired development environment values e.g. ``` sh MATLAB_USER=raphael -MATLAB_LICENSE="#\ BEGIN----...---------END" # See [guzman-raphael/matlab](https://github.com/guzman-raphael/matlab) for detailed instructions on image use hosted [DockerHub](https://hub.docker.com/r/raphaelguzman/matlab) +MATLAB_LICENSE="#\ BEGIN----...---------END" # For image usage instructions see https://github.com/guzman-raphael/matlab, https://hub.docker.com/r/raphaelguzman/matlab MATLAB_VERSION=R2018b MATLAB_HOSTID=XX:XX:XX:XX:XX:XX MATLAB_UID=1000 @@ -34,8 +34,11 @@ MYSQL_TAG=5.7 | Use Case | MATLAB Code | | ---------------------------- | ------------------------------------------------------------------------------ | | Run all tests | `run(tests.Main)` | +| ---------------------------- | ------------------------------------------------------------------------------ | | Run one class of tests | `run(tests.TestTls)` | +| ---------------------------- | ------------------------------------------------------------------------------ | | Run one specific test | `runtests('tests.TestTls/testInsecureConn')` | +| ---------------------------- | ------------------------------------------------------------------------------ | | Run tests based on test name | `import matlab.unittest.TestSuite;` | | | `import matlab.unittest.selectors.HasName;` | | | `import matlab.unittest.constraints.ContainsSubstring;` | From 3fe432aff6c8b30c4e94799af1d6e1c9a1091676 Mon Sep 17 00:00:00 2001 From: guzman-raphael Date: Thu, 21 Nov 2019 12:20:39 -0600 Subject: [PATCH 005/105] Fix styling2. --- README.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/README.md b/README.md index 851444a..f40b423 100644 --- a/README.md +++ b/README.md @@ -39,11 +39,7 @@ MYSQL_TAG=5.7 | ---------------------------- | ------------------------------------------------------------------------------ | | Run one specific test | `runtests('tests.TestTls/testInsecureConn')` | | ---------------------------- | ------------------------------------------------------------------------------ | -| Run tests based on test name | `import matlab.unittest.TestSuite;` | -| | `import matlab.unittest.selectors.HasName;` | -| | `import matlab.unittest.constraints.ContainsSubstring;` | -| | `suite = TestSuite.fromClass(?tests.Main, HasName(ContainsSubstring('Conn')));`| -| | `run(suite)` | +| Run tests based on test name | `import matlab.unittest.TestSuite;`
`import matlab.unittest.selectors.HasName;`
`import matlab.unittest.constraints.ContainsSubstring;`
`suite = TestSuite.fromClass(?tests.Main, HasName(ContainsSubstring('Conn')));`
`run(suite)`| Launch Jupyter Notebook From da86788112de5920640775cb31f4f22170e13a6c Mon Sep 17 00:00:00 2001 From: guzman-raphael Date: Thu, 21 Nov 2019 12:25:09 -0600 Subject: [PATCH 006/105] Fix styling3. --- README.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/README.md b/README.md index f40b423..cca236d 100644 --- a/README.md +++ b/README.md @@ -32,14 +32,10 @@ MYSQL_TAG=5.7 * Run desired tests. Some examples are as follows: | Use Case | MATLAB Code | -| ---------------------------- | ------------------------------------------------------------------------------ | | Run all tests | `run(tests.Main)` | -| ---------------------------- | ------------------------------------------------------------------------------ | | Run one class of tests | `run(tests.TestTls)` | -| ---------------------------- | ------------------------------------------------------------------------------ | | Run one specific test | `runtests('tests.TestTls/testInsecureConn')` | -| ---------------------------- | ------------------------------------------------------------------------------ | -| Run tests based on test name | `import matlab.unittest.TestSuite;`
`import matlab.unittest.selectors.HasName;`
`import matlab.unittest.constraints.ContainsSubstring;`
`suite = TestSuite.fromClass(?tests.Main, HasName(ContainsSubstring('Conn')));`
`run(suite)`| +| Run tests based on test name | `import matlab.unittest.TestSuite;`
`import matlab.unittest.selectors.HasName;`
`import matlab.unittest.constraints.ContainsSubstring;`
`suite = TestSuite.fromClass(?tests.Main, ... `
`HasName(ContainsSubstring('Conn')));`

`run(suite)`| Launch Jupyter Notebook From a18d15df8a7e316e3e2862f26f1c194a2f95cbd0 Mon Sep 17 00:00:00 2001 From: guzman-raphael Date: Thu, 21 Nov 2019 12:25:40 -0600 Subject: [PATCH 007/105] Fix styling4. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cca236d..633ce10 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ MYSQL_TAG=5.7 | Run all tests | `run(tests.Main)` | | Run one class of tests | `run(tests.TestTls)` | | Run one specific test | `runtests('tests.TestTls/testInsecureConn')` | -| Run tests based on test name | `import matlab.unittest.TestSuite;`
`import matlab.unittest.selectors.HasName;`
`import matlab.unittest.constraints.ContainsSubstring;`
`suite = TestSuite.fromClass(?tests.Main, ... `
`HasName(ContainsSubstring('Conn')));`

`run(suite)`| +| Run tests based on test name | `import matlab.unittest.TestSuite;`
`import matlab.unittest.selectors.HasName;`
`import matlab.unittest.constraints.ContainsSubstring;`
`suite = TestSuite.fromClass(?tests.Main, ... `
`HasName(ContainsSubstring('Conn')));`
`run(suite)`| Launch Jupyter Notebook From 093f97af8b526a9984a266443fa83e1a5e92fc23 Mon Sep 17 00:00:00 2001 From: guzman-raphael Date: Thu, 21 Nov 2019 12:26:50 -0600 Subject: [PATCH 008/105] Fix styling5. --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 633ce10..8e222de 100644 --- a/README.md +++ b/README.md @@ -32,10 +32,11 @@ MYSQL_TAG=5.7 * Run desired tests. Some examples are as follows: | Use Case | MATLAB Code | +| ---------------------------- | ------------------------------------------------------------------------------ | | Run all tests | `run(tests.Main)` | | Run one class of tests | `run(tests.TestTls)` | | Run one specific test | `runtests('tests.TestTls/testInsecureConn')` | -| Run tests based on test name | `import matlab.unittest.TestSuite;`
`import matlab.unittest.selectors.HasName;`
`import matlab.unittest.constraints.ContainsSubstring;`
`suite = TestSuite.fromClass(?tests.Main, ... `
`HasName(ContainsSubstring('Conn')));`
`run(suite)`| +| Run tests based on test name | `import matlab.unittest.TestSuite;`
`import matlab.unittest.selectors.HasName;`
`import matlab.unittest.constraints.ContainsSubstring;`
`suite = TestSuite.fromClass(?tests.Main, ... `
`HasName(ContainsSubstring('Conn')));`

`run(suite)`| Launch Jupyter Notebook From 8dc6f1fc1183e8065c5410b179967b5fb5e2a4ca Mon Sep 17 00:00:00 2001 From: guzman-raphael Date: Thu, 21 Nov 2019 12:27:43 -0600 Subject: [PATCH 009/105] Fix styling6. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8e222de..c5e294c 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ MYSQL_TAG=5.7 | Run all tests | `run(tests.Main)` | | Run one class of tests | `run(tests.TestTls)` | | Run one specific test | `runtests('tests.TestTls/testInsecureConn')` | -| Run tests based on test name | `import matlab.unittest.TestSuite;`
`import matlab.unittest.selectors.HasName;`
`import matlab.unittest.constraints.ContainsSubstring;`
`suite = TestSuite.fromClass(?tests.Main, ... `
`HasName(ContainsSubstring('Conn')));`

`run(suite)`| +| Run tests based on test name | `import matlab.unittest.TestSuite;`
`import matlab.unittest.selectors.HasName;`
`import matlab.unittest.constraints.ContainsSubstring;`
`suite = TestSuite.fromClass(?tests.Main, ... `
HasName(ContainsSubstring('Conn')));

`run(suite)`| Launch Jupyter Notebook From f83a0c2f78979138c83a9fc8ae5329486bb98e7c Mon Sep 17 00:00:00 2001 From: guzman-raphael Date: Thu, 21 Nov 2019 12:28:33 -0600 Subject: [PATCH 010/105] Fix styling7. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c5e294c..9701dce 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ MYSQL_TAG=5.7 | Run all tests | `run(tests.Main)` | | Run one class of tests | `run(tests.TestTls)` | | Run one specific test | `runtests('tests.TestTls/testInsecureConn')` | -| Run tests based on test name | `import matlab.unittest.TestSuite;`
`import matlab.unittest.selectors.HasName;`
`import matlab.unittest.constraints.ContainsSubstring;`
`suite = TestSuite.fromClass(?tests.Main, ... `
HasName(ContainsSubstring('Conn')));

`run(suite)`| +| Run tests based on test name | `import matlab.unittest.TestSuite;`
`import matlab.unittest.selectors.HasName;`
`import matlab.unittest.constraints.ContainsSubstring;`
`suite = TestSuite.fromClass(?tests.Main, ... `
`HasName(ContainsSubstring('Conn')));`
`run(suite)`| Launch Jupyter Notebook From c9c06c4a1cc3616009bb71454a202fa0ea239b1d Mon Sep 17 00:00:00 2001 From: guzman-raphael Date: Thu, 21 Nov 2019 12:28:52 -0600 Subject: [PATCH 011/105] Fix styling8. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9701dce..26411d1 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ MYSQL_TAG=5.7 | Run all tests | `run(tests.Main)` | | Run one class of tests | `run(tests.TestTls)` | | Run one specific test | `runtests('tests.TestTls/testInsecureConn')` | -| Run tests based on test name | `import matlab.unittest.TestSuite;`
`import matlab.unittest.selectors.HasName;`
`import matlab.unittest.constraints.ContainsSubstring;`
`suite = TestSuite.fromClass(?tests.Main, ... `
`HasName(ContainsSubstring('Conn')));`
`run(suite)`| +| Run tests based on test name | `import matlab.unittest.TestSuite;`
`import matlab.unittest.selectors.HasName;`
`import matlab.unittest.constraints.ContainsSubstring;`
`suite = TestSuite.fromClass(?tests.Main, ... `
` HasName(ContainsSubstring('Conn')));`
`run(suite)`| Launch Jupyter Notebook From d178b8f6c4c6494a8c467b6857f60b1dfb5c8b2c Mon Sep 17 00:00:00 2001 From: guzman-raphael Date: Thu, 21 Nov 2019 12:29:43 -0600 Subject: [PATCH 012/105] Fix styling9. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 26411d1..bd194c4 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ MYSQL_TAG=5.7 | Run all tests | `run(tests.Main)` | | Run one class of tests | `run(tests.TestTls)` | | Run one specific test | `runtests('tests.TestTls/testInsecureConn')` | -| Run tests based on test name | `import matlab.unittest.TestSuite;`
`import matlab.unittest.selectors.HasName;`
`import matlab.unittest.constraints.ContainsSubstring;`
`suite = TestSuite.fromClass(?tests.Main, ... `
` HasName(ContainsSubstring('Conn')));`
`run(suite)`| +| Run tests based on test name | `import matlab.unittest.TestSuite;`
`import matlab.unittest.selectors.HasName;`
`import matlab.unittest.constraints.ContainsSubstring;`
`suite = TestSuite.fromClass(?tests.Main, ... `
` r HasName(ContainsSubstring('Conn')));`
`run(suite)`| Launch Jupyter Notebook From 0abe3cc4c8a893bb1e4bcd4bffd56d5d3d946a1a Mon Sep 17 00:00:00 2001 From: guzman-raphael Date: Thu, 21 Nov 2019 12:30:29 -0600 Subject: [PATCH 013/105] Fix styling10. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bd194c4..94262a7 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ MYSQL_TAG=5.7 | Run all tests | `run(tests.Main)` | | Run one class of tests | `run(tests.TestTls)` | | Run one specific test | `runtests('tests.TestTls/testInsecureConn')` | -| Run tests based on test name | `import matlab.unittest.TestSuite;`
`import matlab.unittest.selectors.HasName;`
`import matlab.unittest.constraints.ContainsSubstring;`
`suite = TestSuite.fromClass(?tests.Main, ... `
` r HasName(ContainsSubstring('Conn')));`
`run(suite)`| +| Run tests based on test name | `import matlab.unittest.TestSuite;`
`import matlab.unittest.selectors.HasName;`
`import matlab.unittest.constraints.ContainsSubstring;`
`suite = TestSuite.fromClass(?tests.Main, ... `
`--> HasName(ContainsSubstring('Conn')));`
`run(suite)`| Launch Jupyter Notebook From f70c8bdf32223cded340d9d719494662936ad482 Mon Sep 17 00:00:00 2001 From: guzman-raphael Date: Thu, 21 Nov 2019 12:35:20 -0600 Subject: [PATCH 014/105] Fix styling11. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 94262a7..0ffe28f 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ MYSQL_TAG=5.7 | Run all tests | `run(tests.Main)` | | Run one class of tests | `run(tests.TestTls)` | | Run one specific test | `runtests('tests.TestTls/testInsecureConn')` | -| Run tests based on test name | `import matlab.unittest.TestSuite;`
`import matlab.unittest.selectors.HasName;`
`import matlab.unittest.constraints.ContainsSubstring;`
`suite = TestSuite.fromClass(?tests.Main, ... `
`--> HasName(ContainsSubstring('Conn')));`
`run(suite)`| +| Run tests based on test name | `import matlab.unittest.TestSuite;`
`import matlab.unittest.selectors.HasName;`
`import matlab.unittest.constraints.ContainsSubstring;`
`suite = TestSuite.fromClass(?tests.Main, ... `
` HasName(ContainsSubstring('Conn')));`
`run(suite)`| Launch Jupyter Notebook From 08f5d49da5e12633240a9b15df9080d8a5cb6db1 Mon Sep 17 00:00:00 2001 From: guzman-raphael Date: Thu, 21 Nov 2019 12:36:56 -0600 Subject: [PATCH 015/105] Fix styling12. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0ffe28f..6b77bb6 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ MYSQL_TAG=5.7 | Run all tests | `run(tests.Main)` | | Run one class of tests | `run(tests.TestTls)` | | Run one specific test | `runtests('tests.TestTls/testInsecureConn')` | -| Run tests based on test name | `import matlab.unittest.TestSuite;`
`import matlab.unittest.selectors.HasName;`
`import matlab.unittest.constraints.ContainsSubstring;`
`suite = TestSuite.fromClass(?tests.Main, ... `
` HasName(ContainsSubstring('Conn')));`
`run(suite)`| +| Run tests based on test name | `import matlab.unittest.TestSuite;`
`import matlab.unittest.selectors.HasName;`
`import matlab.unittest.constraints.ContainsSubstring;`
`suite = TestSuite.fromClass(?tests.Main, ... `
` HasName(ContainsSubstring('Conn')));`
`run(suite)`| Launch Jupyter Notebook From 2a23182b2e687c637ea2ff810daeb89bd6c78f65 Mon Sep 17 00:00:00 2001 From: guzman-raphael Date: Thu, 21 Nov 2019 12:37:45 -0600 Subject: [PATCH 016/105] Fix styling13. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6b77bb6..44653a4 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ MYSQL_TAG=5.7 | Run all tests | `run(tests.Main)` | | Run one class of tests | `run(tests.TestTls)` | | Run one specific test | `runtests('tests.TestTls/testInsecureConn')` | -| Run tests based on test name | `import matlab.unittest.TestSuite;`
`import matlab.unittest.selectors.HasName;`
`import matlab.unittest.constraints.ContainsSubstring;`
`suite = TestSuite.fromClass(?tests.Main, ... `
` HasName(ContainsSubstring('Conn')));`
`run(suite)`| +| Run tests based on test name | `import matlab.unittest.TestSuite;`
`import matlab.unittest.selectors.HasName;`
`import matlab.unittest.constraints.ContainsSubstring;`
`suite = TestSuite.fromClass(?tests.Main, ... `
`HasName(ContainsSubstring('Conn')));`
`run(suite)`| Launch Jupyter Notebook From 1e1bc73251211c49214c91bff3ee13fa49157630 Mon Sep 17 00:00:00 2001 From: guzman-raphael Date: Thu, 21 Nov 2019 12:38:09 -0600 Subject: [PATCH 017/105] Fix styling14. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 44653a4..3785d54 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ MYSQL_TAG=5.7 | Run all tests | `run(tests.Main)` | | Run one class of tests | `run(tests.TestTls)` | | Run one specific test | `runtests('tests.TestTls/testInsecureConn')` | -| Run tests based on test name | `import matlab.unittest.TestSuite;`
`import matlab.unittest.selectors.HasName;`
`import matlab.unittest.constraints.ContainsSubstring;`
`suite = TestSuite.fromClass(?tests.Main, ... `
`HasName(ContainsSubstring('Conn')));`
`run(suite)`| +| Run tests based on test name | `import matlab.unittest.TestSuite;`
`import matlab.unittest.selectors.HasName;`
`import matlab.unittest.constraints.ContainsSubstring;`
`suite = TestSuite.fromClass(?tests.Main, ... `
`HasName(ContainsSubstring('Conn')));`
`run(suite)`| Launch Jupyter Notebook From e0bbf338e9197ca1e688e491d28d4277d688d1d8 Mon Sep 17 00:00:00 2001 From: guzman-raphael Date: Thu, 21 Nov 2019 12:38:51 -0600 Subject: [PATCH 018/105] Fix styling15. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3785d54..948615b 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ MYSQL_TAG=5.7 | Run all tests | `run(tests.Main)` | | Run one class of tests | `run(tests.TestTls)` | | Run one specific test | `runtests('tests.TestTls/testInsecureConn')` | -| Run tests based on test name | `import matlab.unittest.TestSuite;`
`import matlab.unittest.selectors.HasName;`
`import matlab.unittest.constraints.ContainsSubstring;`
`suite = TestSuite.fromClass(?tests.Main, ... `
`HasName(ContainsSubstring('Conn')));`
`run(suite)`| +| Run tests based on test name | `import matlab.unittest.TestSuite;`
`import matlab.unittest.selectors.HasName;`
`import matlab.unittest.constraints.ContainsSubstring;`
`suite = TestSuite.fromClass(?tests.Main, ... `
    `HasName(ContainsSubstring('Conn')));`
`run(suite)`| Launch Jupyter Notebook From f9ef83cd5b9fd197548231c7bc842ffc93eb0eb0 Mon Sep 17 00:00:00 2001 From: guzman-raphael Date: Thu, 21 Nov 2019 12:49:37 -0600 Subject: [PATCH 019/105] Ignore user's docker-compose. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 8f4adbf..797e5e3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ mym.o build/* +docker-compose.yml *.env notebook/* \ No newline at end of file From 883c572709d5555f7d4a2771457332733168e036 Mon Sep 17 00:00:00 2001 From: guzman-raphael Date: Thu, 21 Nov 2019 14:19:13 -0600 Subject: [PATCH 020/105] Fix typo. --- .travis.yml | 2 +- LNX-docker-compose.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index d766b88..a18c623 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,7 +18,7 @@ slim: &slim script: - license=MATLAB_LICENSE_${MATLAB_VERSION} - export MATLAB_LICENSE=$(eval echo "\$$license") - - docker-compose -f LNX-docker-compose.yml up --exit-code-from dj + - docker-compose -f LNX-docker-compose.yml up --exit-code-from app jobs: include: - <<: *slim diff --git a/LNX-docker-compose.yml b/LNX-docker-compose.yml index f1e943e..07bf58a 100644 --- a/LNX-docker-compose.yml +++ b/LNX-docker-compose.yml @@ -1,4 +1,4 @@ -# docker-compose -f LNX-docker-compose.yml --env-file LNX.env up --build --exit-code-from mym +# docker-compose -f LNX-docker-compose.yml --env-file LNX.env up --build --exit-code-from app version: '2.4' x-net: &net networks: From e97ae3e0211c9779cc0f359cc35040e54387a4ca Mon Sep 17 00:00:00 2001 From: guzman-raphael Date: Thu, 21 Nov 2019 14:21:51 -0600 Subject: [PATCH 021/105] Fix typo in compose. --- LNX-docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LNX-docker-compose.yml b/LNX-docker-compose.yml index 07bf58a..81817d8 100644 --- a/LNX-docker-compose.yml +++ b/LNX-docker-compose.yml @@ -4,7 +4,7 @@ x-net: &net networks: - main services: - mym: + app: <<: *net environment: - DISPLAY From 9abf97ebf81999699260a52607794f0224303f11 Mon Sep 17 00:00:00 2001 From: guzman-raphael Date: Thu, 21 Nov 2019 15:44:04 -0600 Subject: [PATCH 022/105] Add compile step and mym date check. --- .travis.yml | 2 ++ local-docker-compose.yml | 1 + 2 files changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index a18c623..82175af 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,6 +19,8 @@ slim: &slim - license=MATLAB_LICENSE_${MATLAB_VERSION} - export MATLAB_LICENSE=$(eval echo "\$$license") - docker-compose -f LNX-docker-compose.yml up --exit-code-from app + - if [ "distribution/mexa64/mym.mexa64" -nt "distribution/mexmaci64/mym.mexmaci64" ]; then exit 1; fi + - if [ "distribution/mexa64/mym.mexa64" -nt "distribution/mexw64/mym.mexw64" ]; then exit 1; fi jobs: include: - <<: *slim diff --git a/local-docker-compose.yml b/local-docker-compose.yml index 8e3842e..2c1c32b 100644 --- a/local-docker-compose.yml +++ b/local-docker-compose.yml @@ -29,6 +29,7 @@ services: /bin/bash -c " ## Set mym path matlab -nodisplay -r \"\ + run('mex_compilation/compile_linux');\ addpath('./distribution/mexa64');\ savepath;\ \"; From 1b28f1760e22ad2559586f53be81481fa4f288c6 Mon Sep 17 00:00:00 2001 From: guzman-raphael Date: Thu, 21 Nov 2019 15:57:15 -0600 Subject: [PATCH 023/105] Remove unnecessary inheritance. --- +tests/Main.m | 1 - distribution/mexa64/mym.mexa64 | Bin 48000 -> 48000 bytes local-docker-compose.yml | 2 +- 3 files changed, 1 insertion(+), 2 deletions(-) diff --git a/+tests/Main.m b/+tests/Main.m index 362be94..9e643b5 100644 --- a/+tests/Main.m +++ b/+tests/Main.m @@ -1,5 +1,4 @@ classdef Main < ... - tests.Prep & ... tests.TestConnection & ... tests.TestTls end \ No newline at end of file diff --git a/distribution/mexa64/mym.mexa64 b/distribution/mexa64/mym.mexa64 index c87890499b1daec578a6f1b10bb74f7b57b030d9..917502f7378c403f014b36625ec5630e70bf0cbe 100755 GIT binary patch delta 42 zcmV+_0M-A1_X2?T0vDxnQ Date: Fri, 22 Nov 2019 09:50:36 -0600 Subject: [PATCH 024/105] Add mym serialization/deserialization utility for array/cell/struct. --- +tests/+lib/celleq.m | 107 +++++++++++++++++++++++ +tests/+lib/structeq.m | 89 +++++++++++++++++++ +tests/TestExternal.m | 121 ++++++++++++++++++++++++++ +tests/TestTls.m | 2 +- distribution/mexa64/mym.mexa64 | Bin 48000 -> 48192 bytes src/mym.cpp | 153 ++++++++++++++++++++++++++++++++- 6 files changed, 468 insertions(+), 4 deletions(-) create mode 100644 +tests/+lib/celleq.m create mode 100644 +tests/+lib/structeq.m create mode 100644 +tests/TestExternal.m diff --git a/+tests/+lib/celleq.m b/+tests/+lib/celleq.m new file mode 100644 index 0000000..2d5d15d --- /dev/null +++ b/+tests/+lib/celleq.m @@ -0,0 +1,107 @@ +function [result, why] = celleq(cell1, cell2, funh2string, ignorenan) +% CELLEQ performs an equality comparison between two cell arrays by +% recursively comparing the elements of the cell array, their values and +% sub-values +% +% USAGE: +% +% celleq(cell1, cell2) +% Performs a comparison and returns true if all the elements +% of the cell arrays are identical. It will fail if +% elements include function handles or other objects which don't +% have a defined eq method. +% +% [iseq, info] = celleq(cell1, cell2) +% This syntax returns a logical iseq and a second output info which +% is a structure that contains a field "Reason" which gives you a +% text stack of why the difference occurred as well as a field +% "Where" which contains the indices of the element and subelement +% where the comparison failed. If iseq is true, info contains empty +% strings in its fields. +% +% [...] = celleq(cell1, cell2, funh2string, ignorenan) +% Illustrates an alternate syntax for the function with an additional +% input arguments. funh2string, if true, instructs function handle +% comparisons to return true if the string representations of the +% function handles are the same. ignorenan, if true, will return true +% for nan == nan. By default both properties are set to false +% +% METHOD: +% 1. Compare sizes of cell arrays +% 2. Recursively compare the elements of the cell array and keep track of +% the recursion path (to populate the info variable if comparison fails) +% +% EXAMPLE: +% +% c1 = {1:5, 'blah', {'hello', @disp, {[7 6 NaN 3], false}}, 16}; +% c2 = {1:5, 'blah', {'hello', @disp, {[7 6 NaN 3], true }}, 16}; +% celleq(c1, c2) +% [iseq, info] = celleq(c1, c2) +% [iseq, info] = celleq(c1, c2, true) +% [iseq, info] = celleq(c1, c2, true, true) +if nargin < 3 + funh2string = false; +end +if nargin < 4 + ignorenan = false; +end +result = true; % Prove me wrong! +why = struct('Reason','','Where',''); +if any(size(cell1) ~= size(cell2)) + result = false; + why = struct('Reason','Sizes are different','Where',''); + return +end +for i = 1:numel(cell1) + why = struct('Reason','','Where',sprintf('{%d}',i)); + if any(size(cell1{i}) ~= size(cell2{i})) + result = false; + why.Reason = 'Sizes are different'; + return + end + if ~strcmp(class(cell1{i}),class(cell2{i})) + result = false; + why.Reason = 'Different Classes/Types'; + return + end + % At this point we know they have the same size and class + try + whysub = struct('Reason',['Unequal ' class(cell1{i}) 's'],... + 'Where',''); + + switch class(cell1{i}) + case 'cell' + [result, whysub] = tests.lib.celleq(cell1{i},cell2{i},funh2string, ignorenan); + case 'struct' + [result, whysub] = tests.lib.structeq(cell1{i},cell2{i},funh2string, ignorenan); + case 'function_handle' + if funh2string + result = strcmp(func2str(cell1{i}), func2str(cell1{i})); + else + result = false; + end + case {'double', 'single'} + if ignorenan + cell1{i}(isnan(cell1{i})) = 0; + cell2{i}(isnan(cell2{i})) = 0; + elseif any(isnan(cell1{i}(:))) + whysub.Reason = [whysub.Reason ' that contain NaNs']; + end + result = eq(cell1{i},cell2{i}); + otherwise + result = eq(cell1{i},cell2{i}); + end + % result could be a vector + result = all(result(:)); + if ~result + why.Reason = sprintf('Unequal Subcell <- %s',whysub.Reason); + why.Where = [why.Where whysub.Where]; + return; + end + catch ME + result = false; + why.Reason = ['Subcell comparison failed: ' ME.message]; + return + end +end +end \ No newline at end of file diff --git a/+tests/+lib/structeq.m b/+tests/+lib/structeq.m new file mode 100644 index 0000000..68db91d --- /dev/null +++ b/+tests/+lib/structeq.m @@ -0,0 +1,89 @@ +function [result, why] = structeq(struct1, struct2, funh2string, ignorenan) +% STRUCTEQ performs an equality comparison between two structures by +% recursively comparing the elements of the struct array, their fields and +% subfields. This function requires companion function CELLEQ to compare +% two cell arrays. +% +% USAGE: +% +% structeq(struct1, struct2) +% Performs a comparison and returns true if all the subfields and +% properties of the structures are identical. It will fail if +% subfields include function handles or other objects which don't +% have a defined eq method. +% +% [iseq, info] = structeq(struct1, struct2) +% This syntax returns a logical iseq and a second output info which +% is a structure that contains a field "Reason" which gives you a +% text stack of why the difference occurred as well as a field +% "Where" which contains the indices and subfields of the structure +% where the comparison failed. If iseq is true, info contains empty +% strings in its fields. +% +% [...] = structeq(struct1, struct2, funh2string, ignorenan) +% Illustrates an alternate syntax for the function with additional +% input arguments. See the help for CELLEQ for more information on the +% meaning of the arguments +% +% METHOD: +% 1. Compare sizes of struct arrays +% 2. Compare numbers of fields +% 3. Compare field names of the arrays +% 4. For every element of the struct arrays, convert the field values into +% a cell array and do a cell array comparison recursively (this can result +% in multiple recursive calls to CELLEQ and STRUCTEQ) +% +% EXAMPLE: +% % Compare two handle graphics hierarchies +% figure; +% g = surf(peaks(50)); +% rotate3d +% hg1 = handle2struct(gcf); +% set(g,'XDataMode', 'manual'); +% hg2 = handle2struct(gcf); +% +% structeq(hg1, hg2) +% [iseq, info] = structeq(hg1, hg2) +% [iseq, info] = structeq(hg1, hg2, true) +if nargin < 3 + funh2string = false; +end +if nargin < 4 + ignorenan = false; +end +why = struct('Reason','','Where',''); +if any(size(struct1) ~= size(struct2)) + result = false; + why = struct('Reason','Sizes are different',Where,''); + return +end +fields1 = fieldnames(struct1); +fields2 = fieldnames(struct2); +% Check field lengths +if length(fields1) ~= length(fields2) + result = false; + why = struct('Reason','Number of fields are different','Where',''); + return +end +% Check field names +result = tests.lib.celleq(fields1,fields2); +result = all(result); +if ~result + why = struct('Reason','Field names are different','Where',''); + return +end +for i = 1:numel(struct1) + props1 = struct2cell(struct1(i)); + props2 = struct2cell(struct2(i)); + [result, subwhy] = tests.lib.celleq(props1,props2,funh2string,ignorenan); + result = all(result); + if ~result + + [fieldidx, subwhy.Where] = strtok(subwhy.Where, '}'); + fieldidx = str2double(fieldidx(2:end)); + %str2double(regexp(subwhy.Where,'{([0-9]+)}','tokens','once')); + where = sprintf('(%d).%s%s',i,fields1{fieldidx},subwhy.Where(2:end)); + why = struct('Reason',sprintf('Properties are different <- %s',subwhy.Reason),'Where',where); + return + end +end \ No newline at end of file diff --git a/+tests/TestExternal.m b/+tests/TestExternal.m new file mode 100644 index 0000000..d38def4 --- /dev/null +++ b/+tests/TestExternal.m @@ -0,0 +1,121 @@ +classdef TestExternal < tests.Prep + % TestExternal tests external storage serialization/deserialization. + methods (Test) + function testArraySerialization(testCase) + % array serialization test + st = dbstack; + disp(['---------------' st(1).name '---------------']); + + multi(:,:,1) = [4,3;2,7]; + multi(:,:,2) = [0,5;3,8]; + + array_tests = { + [5,10], + [2,3,4;4,5,3;5,9,7], + [5], + 1, + [], + [2.3,2.56,2.45], + [4.5342;123.3145;345.2133], + multi, + 'hello', + '' + }; + + % user cellfun + for i = 1 : length(array_tests) + % disp(array_tests{i}); + % bool = testCase.repack(array_tests{i}) == array_tests{i}; + % testCase.verifyTrue(sum(bool,'all') == numel(bool)); + unpacked = testCase.repack(array_tests{i}); + % testCase.verifyTrue(isequal(array_tests{i},unpacked)); + testCase.verifyTrue(tests.lib.celleq(array_tests(i),{unpacked})); + + end + end + function testStructSerialization(testCase) + % struct serialization test + st = dbstack; + disp(['---------------' st(1).name '---------------']); + + % import matlab.unittest.constraints.IsEqualTo; + % import matlab.unittest.constraints.StructComparator; + % import matlab.unittest.constraints.NumericComparator; + + multi(:,:,1) = [4,3;2,7]; + multi(:,:,2) = [0,5;3,8]; + + array_tests = { + struct(... + 'x',[8,5,3;4,6,7;4,8,2],... + 'y',[4,2,8;5,5,8;3,3,8]... + ), + struct('sample',multi), + struct('lvl1',struct('lvl2',multi)), + struct('sample','hi'), + struct('lvl1',struct('lvl2',{[8,5,3;4,6,7;4,8,2], {'now',{[4,2,8;5,5,8;3,3,8]}}, {multi}})), + % ,struct("sample","bye") + }; + + % user cellfun + for i = 1 : length(array_tests) + % disp(array_tests{i}); + unpacked = testCase.repack(array_tests{i}); + % disp(unpacked); + + % testCase.verifyThat(array_tests{i}, IsEqualTo(unpacked, 'Using', ... + % StructComparator(NumericComparator))) + + testCase.verifyTrue(tests.lib.celleq(array_tests(i),{unpacked})); + end + end + function testCellSerialization(testCase) + % cell serialization test + st = dbstack; + disp(['---------------' st(1).name '---------------']); + + + multi(:,:,1) = [4,3;2,7]; + multi(:,:,2) = [0,5;3,8]; + + array_tests = { + {[8,5,3;4,6,7;4,8,2], [4,2,8;5,5,8;3,3,8]}, + {[8,5,3;4,6,7;4,8,2], [4,2,8;5,5,8;3,3,8], multi}, + {[8,5,3;4,6,7;4,8,2], {0,{[4,2,8;5,5,8;3,3,8]}}, {multi}}, + {'bye', 'now'}, + {[8,5,3;4,6,7;4,8,2], {struct('lvl1',struct('lvl2',multi)),{[4,2,8;5,5,8;3,3,8],'yes'}}, {multi}} + }; + + % user cellfun + for i = 1 : length(array_tests) + % disp(array_tests{i}); + unpacked = testCase.repack(array_tests{i}); + % disp(unpacked); + + testCase.verifyTrue(tests.lib.celleq(array_tests(i),{unpacked})); + end + + % % curr_conn = mym(-1, 'open', testCase.CONN_INFO.host, ... + % % testCase.CONN_INFO.user, testCase.CONN_INFO.password, ... + % % 'false'); + + % orig = {[8,5,3;4,6,7;4,8,2], [4,2,8;5,5,8;3,3,8]}; + + % packed_cell = mym('serialize {M}', orig); + % packed = packed_cell{1}; + % unpacked = mym('deserialize',packed); + + % testCase.verifyTrue(sum(norm(orig{1}-unpacked{1},2)+norm(orig{2}-unpacked{2},2)) == 0); + % % mym('closeall'); + end + end + methods (Static) + function unpacked = repack(data) + % serialization test + + packed_cell = mym('serialize {M}', data); + packed = packed_cell{1}; + unpacked = mym('deserialize',packed); + end + end +end \ No newline at end of file diff --git a/+tests/TestTls.m b/+tests/TestTls.m index 636d64d..b506a83 100644 --- a/+tests/TestTls.m +++ b/+tests/TestTls.m @@ -14,7 +14,7 @@ function testSecureConn(testCase) testCase.verifyTrue(contains(connections{curr_conn+1},'encrypted')); res = mym(curr_conn, 'SHOW STATUS LIKE ''Ssl_cipher'';'); - testCase.verifyTrue(length(res.Value{1}) > 0); + testCase.verifyTrue(length(res.Value{1}) > 0); mym('closeall'); end function testInsecureConn(testCase) diff --git a/distribution/mexa64/mym.mexa64 b/distribution/mexa64/mym.mexa64 index 917502f7378c403f014b36625ec5630e70bf0cbe..543703817672c51680d7ec1626b4268a1ddaaca9 100755 GIT binary patch literal 48192 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 BuP^`r delta 19480 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 diff --git a/src/mym.cpp b/src/mym.cpp index 3b15914..7d84ab2 100644 --- a/src/mym.cpp +++ b/src/mym.cpp @@ -311,7 +311,7 @@ static void field2int(const char*s, enum_field_types t, unsigned int flags, void * This is based on an original by Kimmo Uutela **********************************************************************/ static char* getstring(const mxArray*a) { - int llen = mxGetM(a)*mxGetN(a)*sizeof(mxChar)+1; + int llen = (int) mxGetM(a)* (int) mxGetN(a)*sizeof(mxChar)+1; char*c = (char*) mxCalloc(llen, sizeof(char)); if (mxGetString(a, c, llen)) mexErrMsgTxt("Can\'t copy string in getstring()"); @@ -394,7 +394,7 @@ void mexFunction(int nlhs, mxArray*plhs[], int nrhs, const mxArray*prhs[]) { mexPrintf("cid = %d jarg = %d\n", cid, jarg); /*********************************************************************/ // Parse the result based on the first argument - enum querytype { OPEN, CLOSE, CLOSE_ALL, USE, STATUS, CMD, VERSION } q; + enum querytype { OPEN, CLOSE, CLOSE_ALL, USE, STATUS, CMD, SERIALIZE, DESERIALIZE, VERSION } q; char*query = NULL; if (nrhs<=jarg) q = STATUS; @@ -414,11 +414,15 @@ void mexFunction(int nlhs, mxArray*plhs[], int nrhs, const mxArray*prhs[]) { q = STATUS; else if (!strcasecmp(query, "version")) q = VERSION; + else if (strcasestr(query, "deserialize") != NULL) + q = DESERIALIZE; + else if (strcasestr(query, "serialize") != NULL) + q = SERIALIZE; else q = CMD; } // Check that the arguments are all character strings - if (q!=CMD) + if ((q!=CMD) & (q!=SERIALIZE) & (q!=DESERIALIZE)) for (int j = jarg; jMIN_LEN_ZLIB)) { // compress if needed + // compress field + const uLongf max_len = compressBound(plen[i]); + if (max_len>cmp_buf_len){ + pcmp = (Bytef*)mxRealloc(pcmp, max_len); + cmp_buf_len = max_len; + } + uLongf len = cmp_buf_len; + if (compress(pcmp, &len, (Bytef*)pd[i], plen[i])!=Z_OK) + mexErrMsgTxt("Compression failed"); + const float cmp_rate = plen[i]/(LEN_ZLIB_ID+1.f+sizeof(_uint64)+len); + if (cmp_rate>MIN_CMP_RATE_ZLIB) { + const size_t len_old = plen[i]; + plen[i] = LEN_ZLIB_ID+1+sizeof(_uint64)+len; + pd[i] = (char*)mxRealloc(pd[i], plen[i]); + memcpy(pd[i], (const char*)ZLIB_ID, LEN_ZLIB_ID); + *(pd[i]+LEN_ZLIB_ID) = 0; + + *((_uint64*)(pd[i]+LEN_ZLIB_ID+1)) = (_uint64) len_old; + memcpy(pd[i]+LEN_ZLIB_ID+1+sizeof(_uint64), pcmp, len); + //BUG: *((_uint64*)(pd[i]+LEN_ZLIB_ID+1+sizeof(_uint64))) = (_uint64) len; + } + } + nb += plen[i]; + } + mxFree(query); + mxFree(po); + mxFree(pc); + mxFree(pec); + mxFree(ps); + mxFree(pa); + if (pcmp!=NULL) + mxFree(pcmp); + + // return the serialized items as cell array of unsigned byte vectors + if (nlhs > 0) { + mxArray *cell_array_ptr ; + mxArray *vec ; + cell_array_ptr = mxCreateCellMatrix(nac,1); + if (cell_array_ptr != NULL) { + for (unsigned i=0; i (jarg+1)) mexErrMsgTxt("Version command does not take additional inputs"); From fdc1761e6f5fed46b9a7d1a9390aaa7bc37ab7b2 Mon Sep 17 00:00:00 2001 From: guzman-raphael Date: Mon, 25 Nov 2019 12:28:15 -0600 Subject: [PATCH 025/105] Update class dispose to be at class context. --- +tests/Prep.m | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/+tests/Prep.m b/+tests/Prep.m index fe6f784..c2d500b 100644 --- a/+tests/Prep.m +++ b/+tests/Prep.m @@ -15,7 +15,6 @@ function init(testCase) disp('---------------INIT---------------'); clear functions; - testCase.addTeardown(@testCase.dispose); addpath('./distribution/mexa64'); curr_conn = mym(-1, 'open', testCase.CONN_INFO_ROOT.host, ... @@ -80,14 +79,13 @@ function init(testCase) mym('closeall'); end end - - methods (Static) - function dispose() + methods (TestClassTeardown) + function dispose(testCase) disp('---------------DISP---------------'); warning('off','MATLAB:RMDIR:RemovedFromPath'); - curr_conn = mym(-1, 'open', tests.Prep.CONN_INFO_ROOT.host, ... - tests.Prep.CONN_INFO_ROOT.user, tests.Prep.CONN_INFO_ROOT.password, ... + curr_conn = mym(-1, 'open', testCase.CONN_INFO_ROOT.host, ... + testCase.CONN_INFO_ROOT.user, testCase.CONN_INFO_ROOT.password, ... 'false'); cmd = {... From 145975ea0a80903751c212f2aa35992506485579 Mon Sep 17 00:00:00 2001 From: guzman-raphael Date: Wed, 4 Dec 2019 15:26:32 -0600 Subject: [PATCH 026/105] Stash MD5 details. --- +tests/Prep.m | 3 +- +tests/TestExternal.m | 3 +- +tests/TestInsertFetch.m | 83 +++ .gitignore | 4 +- .vscode/c_cpp_properties.json | 37 ++ .vscode/launch.json | 19 + GetMD5.c | 919 +++++++++++++++++++++++++++++++++ GetMD5.m | 114 ++++ GetMD5.mexa64 | Bin 0 -> 23064 bytes GetMD5_helper.m | 110 ++++ InstallMex.m | 330 ++++++++++++ distribution/mexa64/mym.mexa64 | Bin 48192 -> 48560 bytes libmx.so | Bin 0 -> 1983320 bytes local-docker-compose.yml | 8 + print.txt | 1 + print2.txt | 1 + print3.txt | 1 + print4.txt | 1 + src/mym.cpp | 450 +++++++++++++++- src/mym.h | 25 +- uTest_GetMD5.m | 334 ++++++++++++ 21 files changed, 2434 insertions(+), 9 deletions(-) create mode 100644 +tests/TestInsertFetch.m create mode 100644 .vscode/c_cpp_properties.json create mode 100644 .vscode/launch.json create mode 100644 GetMD5.c create mode 100644 GetMD5.m create mode 100755 GetMD5.mexa64 create mode 100644 GetMD5_helper.m create mode 100644 InstallMex.m create mode 100755 libmx.so create mode 100644 print.txt create mode 100644 print2.txt create mode 100644 print3.txt create mode 100644 print4.txt create mode 100644 uTest_GetMD5.m diff --git a/+tests/Prep.m b/+tests/Prep.m index c2d500b..3a98d30 100644 --- a/+tests/Prep.m +++ b/+tests/Prep.m @@ -22,7 +22,8 @@ function init(testCase) 'false'); res = mym(curr_conn, 'select @@version as version'); - if tests.lib.compareVersions(res.version,'5.8') + disp(res.version); + if tests.lib.compareVersions({char(res.version{1})},'5.8') cmd = {... 'CREATE USER IF NOT EXISTS ''datajoint''@''%%'' ' 'IDENTIFIED BY ''datajoint'';' diff --git a/+tests/TestExternal.m b/+tests/TestExternal.m index d38def4..41f1c29 100644 --- a/+tests/TestExternal.m +++ b/+tests/TestExternal.m @@ -19,7 +19,8 @@ function testArraySerialization(testCase) [4.5342;123.3145;345.2133], multi, 'hello', - '' + '', + uint8([29, 117, 30, 46, 30, 116, 250, 248, 74, 180, 133, 253, 232, 239, 114, 190]) }; % user cellfun diff --git a/+tests/TestInsertFetch.m b/+tests/TestInsertFetch.m new file mode 100644 index 0000000..fe106bc --- /dev/null +++ b/+tests/TestInsertFetch.m @@ -0,0 +1,83 @@ +classdef TestInsertFetch < tests.Prep + % TestExternal tests external storage serialization/deserialization. + methods (Test) + function testInsertFetch(testCase) + % array serialization test + st = dbstack; + disp(['---------------' st(1).name '---------------']); + curr_conn = mym(-1, 'open', testCase.CONN_INFO.host, testCase.CONN_INFO.user, ... + testCase.CONN_INFO.password, 'false'); + + mym(curr_conn, 'drop database if exists `djtest_insert`;'); + mym(curr_conn, 'create database `djtest_insert`;'); + + + data = 'raphael'; + ret = testCase.check(curr_conn, 'varchar(7)','S',data); + % disp(ret); + % disp(data); + assert(all(ret == data)); + + data = int64([1;2]); + ret = testCase.check(curr_conn, 'longblob','M',data); + % disp(ret); + % disp(data); + assert(all(ret == data)); + + + data = 'ýýýý'; + ret = testCase.check(curr_conn, 'varchar(8)','S',data); + % disp(ret); + % disp(data); + assert(all(ret == data)); + + + data = '1d751e2e-1e74-faf8-4ab4-85fde8ef72be'; + data = strrep(data, '-', ''); + v = data; + hexstring = v'; + reshapedString = reshape(hexstring,2,16); + hexMtx = reshapedString.'; + decMtx = hex2dec(hexMtx); + v = uint8(decMtx)'; + + ret = testCase.check(curr_conn, 'binary(16)','B',v); + % disp(ret); + % disp(v); + assert(all(ret == v)); + + + data = '1d751e2e-1e74-faf8-4ab4-85fde8ef72be'; + data = strrep(data, '-', ''); + v = data; + hexstring = v'; + reshapedString = reshape(hexstring,2,16); + hexMtx = reshapedString.'; + decMtx = hex2dec(hexMtx); + v = uint8(decMtx); + v_char = char(v)'; + + ret = testCase.check(curr_conn, 'varchar(24)','S',v_char); + % disp(ret); + % disp(v_char); + assert(all(ret == v_char)); + + + + mym('closeall'); + end + end + methods (Static) + function ret = check(conn_id, datatype, flag, data) + mym(conn_id, ['create table `djtest_insert`.`test_' datatype '` (id int, name ' datatype ' comment ":' datatype ':");']); + + + % mym(curr_conn, 'insert into `djtest_insert`.`test` (id, name) values (0, "raphael");'); + mym(conn_id, ['INSERT INTO `djtest_insert`.`test_' datatype '` (`id`,`name`) VALUES (0,"{' flag '}") '],data); + + + res = mym(conn_id, ['select * from `djtest_insert`.`test_' datatype '`']); + ret = res.name{1}; + end + end +end \ No newline at end of file diff --git a/.gitignore b/.gitignore index 797e5e3..8ba7307 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ mym.o -build/* +build docker-compose.yml *.env -notebook/* \ No newline at end of file +notebook \ No newline at end of file diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..30b660b --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,37 @@ +{ + "configurations": [ + { + "name": "LNX", + "includePath": [ + "/usr/lib/gcc/x86_64-linux-gnu/6.3.0/include", + "/usr/include/c++/6.3.0", + "/usr/include/c++/6.3.0/x86_64-linux-gnu", + "/usr/include/c++/6.3.0/backward", + "/home/muser/.MATLAB/extern/include", + "${workspaceRoot}" + ], + "defines": [ + "_DEBUG", + "UNICODE", + "_UNICODE" + ], + "intelliSenseMode": "msvc-x64", + "browse": { + "path": [ + "/usr/lib/gcc/x86_64-linux-gnu/6.3.0/include", + "/usr/include/c++/6.3.0", + "/usr/include/c++/6.3.0/x86_64-linux-gnu", + "/usr/include/c++/6.3.0/backward", + "/home/muser/.MATLAB/extern/include", + "${workspaceRoot}" + ], + "limitSymbolsToIncludedHeaders": true, + "databaseFilename": "" + }, + "compilerPath": "/usr/bin/g++", + "cStandard": "c11", + "cppStandard": "c++17" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..b9f2c14 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,19 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "gdb", + "request": "attach", + "name": "Attach to gdbserver", + "executable": "distribution/mexa64/mym.mexa64", + "target": "localhost:9091", + "remote": true, + "cwd": "${workspaceRoot}", + "gdbpath": "/usr/bin/gdb", + "autorun": [] + } + ] +} \ No newline at end of file diff --git a/GetMD5.c b/GetMD5.c new file mode 100644 index 0000000..329c191 --- /dev/null +++ b/GetMD5.c @@ -0,0 +1,919 @@ +// GetMD5.c +// GetMD5 - 128 bit MD5 checksum: file, string, array, byte stream +// This function calculates a 128 bit checksum for arrays or files. +// Digest = GetMD5(Data, Mode, Format) +// INPUT: +// Data: File name or array. +// Mode: CHAR to declare the type of the 1st input. Not case-sensitive. +// 'File': Data is a file name as CHAR. +// '8Bit': If Data is a CHAR array, only the 8 bit ASCII part is +// used. Then the digest is the same as for a ASCII text +// file e.g. created by: FWRITE(FID, Data, 'uchar'). +// This is ignored if Data is not of type CHAR. +// 'Binary': The MD5 sum is obtained for the contents of Data. +// This works for numerical, CHAR and LOGICAL arrays. +// 'Array': Include the class and dimensions of Data in the MD5 +// sum. This can be applied for (nested) structs, cells +// and sparse arrays also. +// Optional. Default: '8Bit' for CHAR, 'Binary' otherwise. +// Format: CHAR, format of the output. Only the first character matters. +// The upper/lower case matters for 'hex' only. +// 'hex': [1 x 32] lowercase hexadecimal CHAR. +// 'HEX': [1 x 32] uppercase hexadecimal CHAR. +// 'double': [1 x 16] double vector with UINT8 values. +// 'uint8': [1 x 16] uint8 vector. +// 'base64': [1 x 22] CHAR, encoded to base 64 (A:Z,a:z,0:9,+,/). +// The CHAR is not padded to keep it short. +// Optional, default: 'hex'. +// +// OUTPUT: +// Digest: A 128 bit number is replied in the specified format. +// +// NOTE: +// * The M-file GetMD5_helper is called for sparse arrays, function handles, +// java and user-defined objects . +// * This is at least 2 times faster than the Java method. +// +// EXAMPLES: +// Three methods to get the MD5 of a file: +// 1. Direct file access (recommended): +// MD5 = GetMD5(which('GetMD5.m'), 'File') +// 2. Import the file to a CHAR array (no text mode for exact line breaks!): +// FID = fopen(which('GetMD5.m'), 'r'); +// S = fread(FID, inf, 'uchar=>char'); +// fclose(FID); +// MD5 = GetMD5(S, '8bit') +// 3. Import file as a byte stream: +// FID = fopen(which('GetMD5.m'), 'r'); +// S = fread(FID, inf, 'uint8=>uint8'); +// fclose(FID); +// MD5 = GetMD5(S, 'bin'); % 'bin' is optional here +// +// Test data: +// GetMD5(char(0:511), '8bit', 'HEX') % Consider 8bit part only +// % => F5C8E3C31C044BAE0E65569560B54332 +// GetMD5(char(0:511), 'bin') % Matlab's CHAR are 16 bit! +// % => 3484769D4F7EBB88BBE942BB924834CD +// GetMD5(char(0:511), 'array') % Consider 16 bit, type and size +// % => b9a955ae730b25330d4f4ebb0a51e8f0 +// GetMD5('abc') % implicit: 8bit for CHAR input +// % => 900150983cd24fb0d6963f7d28e17f72 +// +// COMPILE: +// On demand a C-compiler must be installed at first, see: "mex -setup" +// Automatic: Call GetMD5 without inputs. +// Manually: +// mex -O GetMD5.c +// Consider C99 comments under Linux: +// mex -O CFLAGS="\$CFLAGS -std=c99" GetMD5.c +// Pre-compiled MEX files can be downloaded: http:\\www.n-simon.de\mex +// Run the unit-test uTest_GetMD5 after the compilation. +// +// Tested: Matlab 6.5, 7.7, 7.8, 7.13, 8.6, 9.1, 9.5, WinXP/32, Win7&10/64 +// Compiler: LCC2.4/3.8, BCC5.5, OWC1.8, MSVC2008/2010/2017 +// Assumed Compatibility: higher Matlab versions, Mac, Linux +// Author: Jan Simon, Heidelberg, (C) 2006-2019 matlab.2010(a)n(MINUS)simon.de +// License: BSD. This program is based on: +// RFC 1321, MD5 Message-Digest Algorithm, April 1992 +// RSA Data Security, Inc. MD5 Message Digest Algorithm +// Implementation: Alexander Peslyak +// +// See also: CalcCRC32, DataHash. +// +// Michael Kleder has published a Java call to compute the MD5 and SHA sums: +// http://www.mathworks.com/matlabcentral/fileexchange/8944 + +/******************************************************************************* + * The MD5-part is based on: + * + * This is an OpenSSL-compatible implementation of the RSA Data Security, Inc. + * MD5 Message-Digest Algorithm (RFC 1321). + * + * Homepage: + * http://openwall.info/wiki/people/solar/software/public-domain-source-code/md5 + * + * Author: + * Alexander Peslyak, better known as Solar Designer + * + * This software was written by Alexander Peslyak in 2001. No copyright is + * claimed, and the software is hereby placed in the public domain. + ******************************************************************************* + */ + +/* +% $JRev: R5k V:066 Sum:4vIkvzaOU7o4 Date:04-Mar-2019 16:20:23 $ +% $License: BSD (use/copy/change/redistribute on own risk, mention the author) $ +% $UnitTest: uTest_GetMD5 $ +% $File: Tools\Mex\Source\GetMD5.c $ +% History: +% 011: 20-Oct-2006 20:50, [16 x 1] -> [1 x 16] replied as double. +% 012: 01-Nov-2006 23:10, BUGFIX: hex output for 'Hex' input now. +% 015: 02-Oct-2008 14:47, Base64 output. +% 017: 19-Oct-2008 22:33, Accept numerical arrays as byte stream. +% 023: 15-Dec-2009 16:53, BUGFIX: UINT32 has 32 bits on 64 bit systems now. +% Thanks to Sebastiaan Breedveld! +% 030: 17-Mar-2010 11:38, UINT8 output. +% 032: 06-Jul-2010 23:23, Indirect CONST pointer in ToHex for BCC5.5. +% 037: 14-May-2011 13:14, Default input type: char->byte. +% 042: 27-Jan-2015 23:00, 64 bit arrays, nicer error messages, 10% faster. +% "CalcMD5" -> "GetMD5". +% 046: 16-Feb-2015 00:10, "Array" type: consider type and dimensions. +% The "Array" type works for cells and structs also. +% 050: 09-Mar-2015 23:08, Faster hash code of Alexander Peslyak. +% 060: 04-Jun-2016 22:22, Fixed compile error on Macs. +% Thanks to Jonas Zimmermann. +% 061: 16-Oct-2017 23:56, Compilation failed if _LITTLE_ENDIAN is undefined. +% 062: 30-Jan-2018 01:41, Crashed for NULL pointer in data array. +% 064: 27-Aug-2018 00:53, String class caught in GetMD5_Helper. +*/ + +#define __STDC_WANT_LIB_EXT1__ 1 + +// Headers: +#include +#include +#include +#include +#include "mex.h" + +// Assume 32 bit addressing for Matlab 6.5: +// See MEX option "compatibleArrayDims" for MEX in Matlab >= 7.7. +#ifndef MWSIZE_MAX +# define mwSize int32_T // Defined in tmwtypes.h +# define mwIndex int32_T +# define MWSIZE_MAX MAX_int32_T +#endif + +// Directive for endianess: +#if !defined(_LITTLE_ENDIAN) +# if !defined(_BIG_ENDIAN) +# define _LITTLE_ENDIAN 1 +# else +# define _LITTLE_ENDIAN !_BIG_ENDIAN +# endif +#endif + +// Safe string length for fieldnames: +#if defined _MSC_VER || \ + (defined(__STDC_LIB_EXT1__) && __STDC_WANT_LIB_EXT1__ >= 1) +# define STRING_LENGTH(s,n) strnlen_s(s,n) +#else +# define STRING_LENGTH(s,n) strlen(s) +#endif + +// Strange objects can cause an infinite recursion and kill Matlab. So limit the +// recursion depths for a useful error message: +#define MAX_RECURSION 500 +static int RecursionCount; + +// Undocumented API function, required to forward string objects to the helper +// function: +// bool mxIsOpaque(const mxArray *); + +// MD5 part: ------------------------------------------------------------------- +typedef struct { + uint32_T lo, hi; + uint32_T a, b, c, d; + uchar_T buffer[64]; + uint32_T block[16]; +} MD5_CTX; + +// Prototypes: +void MD5_Init(MD5_CTX *ctx); +void MD5_Update(MD5_CTX *ctx, uchar_T *data, mwSize size); +void MD5_Final(uchar_T *digest, MD5_CTX *ctx); +static uchar_T *MD5_Body(MD5_CTX *ctx, uchar_T *data, mwSize size); + +/* The basic MD5 functions. + * + * F and G are optimized compared to their RFC 1321 definitions for + * architectures that lack an AND-NOT instruction, just like in Colin Plumb's + * implementation. + */ +#define F(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) +#define G(x, y, z) ((y) ^ ((z) & ((x) ^ (y)))) +#define H(x, y, z) ((x) ^ (y) ^ (z)) +#define I(x, y, z) ((y) ^ ((x) | ~(z))) + +/* The MD5 transformation for all four rounds. */ +#define STEP(f, a, b, c, d, x, t, s) \ + (a) += f((b), (c), (d)) + (x) + (t); \ + (a) = (((a) << (s)) | (((a) & 0xffffffff) >> (32 - (s)))); \ + (a) += (b); + +/* SET reads 4 input bytes in little-endian byte order and stores them in a + * properly aligned word in host byte order. + * + * The check for little-endian architectures that tolerate unaligned memory + * accesses is just an optimization. Nothing will break if it doesn't work. + */ +#if _LITTLE_ENDIAN +# define SET(n) (*(uint32_T *)&ptr[(n) * 4]) +# define GET(n) SET(n) +#else +# define SET(n) \ + (ctx->block[(n)] = \ + (uint32_T)ptr[(n) * 4] | \ + ((uint32_T)ptr[(n) * 4 + 1] << 8) | \ + ((uint32_T)ptr[(n) * 4 + 2] << 16) | \ + ((uint32_T)ptr[(n) * 4 + 3] << 24)) +# define GET(n) (ctx->block[(n)]) +#endif + +// Matlab part: ---------------------------------------------------------------- +// Length of the file buffer (must be < 2^31 for 32 bit machines): +#define BUFFER_LEN 1024 +static uchar_T buffer[BUFFER_LEN]; + +typedef enum {ASCII_m, ARRAY_m, BINARY_m, FILE_m} Method_t; + +typedef struct StringRec { + const char_T *string; + int index; +} StringRec; + +// Error and warning messages: +#define ERR_HEAD "*** GetMD5[mex]: " +#define ERR_ID "JSimon:GetMD5:" +#define ERROR(id,msg) mexErrMsgIdAndTxt(ERR_ID id, ERR_HEAD msg); +#define ERROR3(id,msg,arg) mexErrMsgIdAndTxt(ERR_ID id, ERR_HEAD msg, arg); + +#define WARN_HEAD "### GetMD5[mex]: " +#define WARN(id,msg) mexWarnMsgIdAndTxt(ERR_ID id, WARN_HEAD msg); + +// Prototypes: +void ToHex (const uchar_T In[16], char *Out, int LowerCase); +void ToBase64(const uchar_T In[16], char *Out); + +void ProcessBin (uchar_T *data, mwSize N, uchar_T digest[16]); +void ProcessFile (char *FileName, uchar_T digest[16]); +void ProcessChar (mxChar *data, mwSize N, uchar_T digest[16]); +void ProcessArray(const mxArray *V, uchar_T digest[16]); +void ArrayCore (MD5_CTX *context, const mxArray *V); +void StructCore (MD5_CTX *context, const mxArray *V, mwSize nElem); +int CompareStringRec(const void *a, const void *b); + +// =============================== FUNCTIONS =================================== + +// ***************************************************************************** +// ** MD5 part: +// ***************************************************************************** + +// MD5 initialization. Begins an MD5 operation, writing a new context: +void MD5_Init(MD5_CTX *ctx) +{ + // Load magic initialization constants: + ctx->a = 0x67452301; + ctx->b = 0xefcdab89; + ctx->c = 0x98badcfe; + ctx->d = 0x10325476; + + ctx->lo = 0; + ctx->hi = 0; +} + +// This processes one or more 64-byte data blocks, but does NOT update +// the bit counters. There are no alignment requirements. +static uchar_T *MD5_Body(MD5_CTX *ctx, uchar_T *data, mwSize size) +{ + uchar_T *ptr; + uint32_T a, b, c, d, + saved_a, saved_b, saved_c, saved_d; + + ptr = data; + + a = ctx->a; + b = ctx->b; + c = ctx->c; + d = ctx->d; + + do { + saved_a = a; + saved_b = b; + saved_c = c; + saved_d = d; + +// Round 1 + STEP(F, a, b, c, d, SET(0), 0xd76aa478, 7) + STEP(F, d, a, b, c, SET(1), 0xe8c7b756, 12) + STEP(F, c, d, a, b, SET(2), 0x242070db, 17) + STEP(F, b, c, d, a, SET(3), 0xc1bdceee, 22) + STEP(F, a, b, c, d, SET(4), 0xf57c0faf, 7) + STEP(F, d, a, b, c, SET(5), 0x4787c62a, 12) + STEP(F, c, d, a, b, SET(6), 0xa8304613, 17) + STEP(F, b, c, d, a, SET(7), 0xfd469501, 22) + STEP(F, a, b, c, d, SET(8), 0x698098d8, 7) + STEP(F, d, a, b, c, SET(9), 0x8b44f7af, 12) + STEP(F, c, d, a, b, SET(10), 0xffff5bb1, 17) + STEP(F, b, c, d, a, SET(11), 0x895cd7be, 22) + STEP(F, a, b, c, d, SET(12), 0x6b901122, 7) + STEP(F, d, a, b, c, SET(13), 0xfd987193, 12) + STEP(F, c, d, a, b, SET(14), 0xa679438e, 17) + STEP(F, b, c, d, a, SET(15), 0x49b40821, 22) + +// Round 2 + STEP(G, a, b, c, d, GET(1), 0xf61e2562, 5) + STEP(G, d, a, b, c, GET(6), 0xc040b340, 9) + STEP(G, c, d, a, b, GET(11), 0x265e5a51, 14) + STEP(G, b, c, d, a, GET(0), 0xe9b6c7aa, 20) + STEP(G, a, b, c, d, GET(5), 0xd62f105d, 5) + STEP(G, d, a, b, c, GET(10), 0x02441453, 9) + STEP(G, c, d, a, b, GET(15), 0xd8a1e681, 14) + STEP(G, b, c, d, a, GET(4), 0xe7d3fbc8, 20) + STEP(G, a, b, c, d, GET(9), 0x21e1cde6, 5) + STEP(G, d, a, b, c, GET(14), 0xc33707d6, 9) + STEP(G, c, d, a, b, GET(3), 0xf4d50d87, 14) + STEP(G, b, c, d, a, GET(8), 0x455a14ed, 20) + STEP(G, a, b, c, d, GET(13), 0xa9e3e905, 5) + STEP(G, d, a, b, c, GET(2), 0xfcefa3f8, 9) + STEP(G, c, d, a, b, GET(7), 0x676f02d9, 14) + STEP(G, b, c, d, a, GET(12), 0x8d2a4c8a, 20) + +// Round 3 + STEP(H, a, b, c, d, GET(5), 0xfffa3942, 4) + STEP(H, d, a, b, c, GET(8), 0x8771f681, 11) + STEP(H, c, d, a, b, GET(11), 0x6d9d6122, 16) + STEP(H, b, c, d, a, GET(14), 0xfde5380c, 23) + STEP(H, a, b, c, d, GET(1), 0xa4beea44, 4) + STEP(H, d, a, b, c, GET(4), 0x4bdecfa9, 11) + STEP(H, c, d, a, b, GET(7), 0xf6bb4b60, 16) + STEP(H, b, c, d, a, GET(10), 0xbebfbc70, 23) + STEP(H, a, b, c, d, GET(13), 0x289b7ec6, 4) + STEP(H, d, a, b, c, GET(0), 0xeaa127fa, 11) + STEP(H, c, d, a, b, GET(3), 0xd4ef3085, 16) + STEP(H, b, c, d, a, GET(6), 0x04881d05, 23) + STEP(H, a, b, c, d, GET(9), 0xd9d4d039, 4) + STEP(H, d, a, b, c, GET(12), 0xe6db99e5, 11) + STEP(H, c, d, a, b, GET(15), 0x1fa27cf8, 16) + STEP(H, b, c, d, a, GET(2), 0xc4ac5665, 23) + +// Round 4 + STEP(I, a, b, c, d, GET(0), 0xf4292244, 6) + STEP(I, d, a, b, c, GET(7), 0x432aff97, 10) + STEP(I, c, d, a, b, GET(14), 0xab9423a7, 15) + STEP(I, b, c, d, a, GET(5), 0xfc93a039, 21) + STEP(I, a, b, c, d, GET(12), 0x655b59c3, 6) + STEP(I, d, a, b, c, GET(3), 0x8f0ccc92, 10) + STEP(I, c, d, a, b, GET(10), 0xffeff47d, 15) + STEP(I, b, c, d, a, GET(1), 0x85845dd1, 21) + STEP(I, a, b, c, d, GET(8), 0x6fa87e4f, 6) + STEP(I, d, a, b, c, GET(15), 0xfe2ce6e0, 10) + STEP(I, c, d, a, b, GET(6), 0xa3014314, 15) + STEP(I, b, c, d, a, GET(13), 0x4e0811a1, 21) + STEP(I, a, b, c, d, GET(4), 0xf7537e82, 6) + STEP(I, d, a, b, c, GET(11), 0xbd3af235, 10) + STEP(I, c, d, a, b, GET(2), 0x2ad7d2bb, 15) + STEP(I, b, c, d, a, GET(9), 0xeb86d391, 21) + + a += saved_a; + b += saved_b; + c += saved_c; + d += saved_d; + + ptr += 64; + } while (size -= 64); + + ctx->a = a; + ctx->b = b; + ctx->c = c; + ctx->d = d; + + return ptr; +} + +void MD5_Update(MD5_CTX *ctx, uchar_T *data, mwSize size) +{ + uint32_T saved_lo; + mwSize used, free; + + // Update number of bytes: + saved_lo = ctx->lo; + if ((ctx->lo = (saved_lo + size) & 0x1fffffff) < saved_lo) { + ctx->hi++; + } + ctx->hi += (uint32_T) (size >> 29); + + // Process blocks with less than 64 bytes: + used = (saved_lo & 0x3f); + if (used) { + free = 64 - used; + if (size < free) { + memcpy(&ctx->buffer[used], data, size); + return; + } + + memcpy(&ctx->buffer[used], data, free); + data = data + free; + size -= free; + MD5_Body(ctx, ctx->buffer, 64); + } + + // Process the rest of the data in 64 byte blocks: + if (size >= 64) { + data = MD5_Body(ctx, data, size & ~(mwSize)0x3f); + size &= 0x3f; + } + + // Copy remaining bytes to the buffer: + memcpy(ctx->buffer, data, size); +} + +void MD5_Final(uchar_T *result, MD5_CTX *ctx) +{ + mwSize used, free; + + // Padding: + used = ctx->lo & 0x3f; + ctx->buffer[used++] = 0x80; + free = 64 - used; + if (free < 8) { + memset(&ctx->buffer[used], 0, free); + MD5_Body(ctx, ctx->buffer, 64); + used = 0; + free = 64; + } + + memset(&ctx->buffer[used], 0, free - 8); + + // Encode number of bits: + ctx->lo <<= 3; + ctx->buffer[56] = ctx->lo; + ctx->buffer[57] = ctx->lo >> 8; + ctx->buffer[58] = ctx->lo >> 16; + ctx->buffer[59] = ctx->lo >> 24; + ctx->buffer[60] = ctx->hi; + ctx->buffer[61] = ctx->hi >> 8; + ctx->buffer[62] = ctx->hi >> 16; + ctx->buffer[63] = ctx->hi >> 24; + + MD5_Body(ctx, ctx->buffer, 64); + + // Copy hash to the output: + result[0] = ctx->a; + result[1] = ctx->a >> 8; + result[2] = ctx->a >> 16; + result[3] = ctx->a >> 24; + result[4] = ctx->b; + result[5] = ctx->b >> 8; + result[6] = ctx->b >> 16; + result[7] = ctx->b >> 24; + result[8] = ctx->c; + result[9] = ctx->c >> 8; + result[10] = ctx->c >> 16; + result[11] = ctx->c >> 24; + result[12] = ctx->d; + result[13] = ctx->d >> 8; + result[14] = ctx->d >> 16; + result[15] = ctx->d >> 24; + + // Clean up sensitive data: + memset(ctx, 0, sizeof(*ctx)); +} + +// ***************************************************************************** +// ** Matlab part: +// ***************************************************************************** + +// 8Bit ASCII part of CHAR: ==================================================== +void ProcessChar(mxChar *array, mwSize inputLen, uchar_T digest[16]) +{ + // Process string: Matlab stores strings as mxChar, which are 2 bytes per + // character. This function considers the first byte of each CHAR only, which + // is equivalent to calculate the sum after a conversion to a ASCII uchar_T + // string. + MD5_CTX context; + mwSize Chunk; + uchar_T *bufferP, *bufferEnd = buffer + BUFFER_LEN, *arrayP; + + arrayP = (uchar_T *) array; // uchar_T *, not mxChar *! + + MD5_Init(&context); + + // Copy chunks of input data - only the first byte of each mxChar: + Chunk = inputLen / BUFFER_LEN; + while (Chunk--) { + bufferP = buffer; + while (bufferP < bufferEnd) { + *bufferP++ = *arrayP; + arrayP += 2; + } + + MD5_Update(&context, buffer, (mwSize) BUFFER_LEN); + } + + // Last chunk: + Chunk = inputLen % BUFFER_LEN; + if (Chunk != 0) { + bufferEnd = buffer + Chunk; + bufferP = buffer; + while (bufferP < bufferEnd) { + *bufferP++ = *arrayP; + arrayP += 2; + } + + MD5_Update(&context, buffer, Chunk); + } + + MD5_Final(digest, &context); +} + +// Array of any type: ========================================================== +void ProcessArray(const mxArray *V, uchar_T digest[16]) +{ + // The type, dimension and contents of the array are considered. This works + // for cells and structs also. + // Here only the initialization and finalization of the context is performed, + // while the actual processing is done in ArrayCore, which allows recursion + // for cells and structs. + MD5_CTX context; + + RecursionCount = 0; // Reset global recursion counter + + MD5_Init(&context); + ArrayCore(&context, V); + MD5_Final(digest, &context); +} + +// ----------------------------------------------------------------------------- +void ArrayCore(MD5_CTX *context, const mxArray *V) +{ + // Process an array considering the type and dimensions. Cells and Structs + // call this function recursively for each cell element or field. + // The header before the data block is: [ClassName, nDim, Dims]. The ClassName + // is a string, because the ClassID number has been changed during different + // Matlab versions in the past. + // Sparse arrays, function handles, java- and user-defined classes are + // forwarded to the M-function GetMD5_helper, where the user can defined how + // the data is converted to a byte stream. + + uchar_T *dataReal, *dataImag; + mxClassID ClassID; + const mwSize *Dim, nullDim[2] = {0,0}; + mwSize nDim, nElem, iElem, i, lenHeader; + int64_T *header; + size_t ElemSize, Len; + int nField, iField, ok; + bool dataOpaq; + mxArray *Arg[1]; + const char *FieldName, *ClassName; + + // Get header information of the array: + if (V != NULL) { + nDim = mxGetNumberOfDimensions(V); + nElem = mxGetNumberOfElements(V); + ClassID = mxGetClassID(V); + ClassName = mxGetClassName(V); + ElemSize = mxGetElementSize(V); + Dim = mxGetDimensions(V); + dataReal = (uchar_T *) mxGetData(V); + dataImag = (uchar_T *) mxGetImagData(V); + dataOpaq = mxIsOpaque(V); + + // Forward sparse arrays to M-helper function: + if (mxIsSparse(V)) { + ClassID = mxUNKNOWN_CLASS; + } + + + } else { // NULL pointer is equivalent to [0 x 0] double matrix: + nDim = 2; + nElem = 0; + ClassID = mxDOUBLE_CLASS; + ClassName = "double"; + ElemSize = sizeof(double); + Dim = nullDim; + dataReal = (uchar_T *) NULL; + dataImag = (uchar_T *) NULL; + dataOpaq = false; + } + + // mxGetDimensions replies [1x1] for opaque arrays, e.g. the string class. + // They are forwarded to the helper function, where SIZE() replies the true + // dimensions. + if (!dataOpaq) { + // Consider class as name, not as ClassID, because the later might change + // with the Matlab version: + Len = strlen(ClassName); + MD5_Update(context, (uchar_T *) ClassName, Len * sizeof(char)); + + // Encode dimensions as [nDim, Dim]: + // Size as int64_T to get the same hash under 32 and 64 bit systems: + lenHeader = 1 + nDim; + header = (int64_T *) mxCalloc(lenHeader, sizeof(int64_T)); + header[0] = (int64_T) nDim; + for (i = 0; i < nDim; i++) { + header[i + 1] = (int64_T) Dim[i]; + } + MD5_Update(context, (uchar_T *) header, lenHeader * sizeof(int64_T)); + mxFree(header); + } + + // Include the contents of the array: + switch (ClassID) { + case mxLOGICAL_CLASS: // Elementary array: ------------------------------ + case mxCHAR_CLASS: + case mxDOUBLE_CLASS: + case mxSINGLE_CLASS: + case mxINT8_CLASS: + case mxUINT8_CLASS: + case mxINT16_CLASS: + case mxUINT16_CLASS: + case mxINT32_CLASS: + case mxUINT32_CLASS: + case mxINT64_CLASS: + case mxUINT64_CLASS: + MD5_Update(context, dataReal, nElem * ElemSize); + if (dataImag != NULL) { + MD5_Update(context, dataImag, nElem * ElemSize); + } + break; + + case mxCELL_CLASS: // Cell array - recursion: -------------------------- + for (iElem = 0; iElem < nElem; iElem++) { + ArrayCore(context, mxGetCell(V, iElem)); + } + break; + + case mxSTRUCT_CLASS: // Struct array: Fieldnames + recursion: ------------ + StructCore(context, V, nElem); + break; + + default: // FUNCTION, VOID, OPAQUE, UNKNOWN CLASS: ----------------------- + // Treat deep recursion as an error: + if (++RecursionCount > MAX_RECURSION) { + ERROR("DeepRecursion", "Cannot serialize recursive data type.\n" + "Try: GetMD5(getByteStreamFromArray(Data))"); + } + + // Call the M-helper function to be more flexible: + ok = mexCallMATLAB(1, Arg, 1, &V, "GetMD5_helper"); + if (ok != 0) { + ERROR("HelperFailed", "Calling GetMD5_helper failed."); + } + + // Get hash for array replied by the helper: + if (mxIsUint8(Arg[0])) { // Byte stream: + MD5_Update(context, (uchar_T *) mxGetData(Arg[0]), + mxGetNumberOfElements(Arg[0])); + } else { // Not a byte stream, recursive call: + ArrayCore(context, Arg[0]); + } + + // Clean up: + if (Arg[0] != NULL) { + mxDestroyArray(Arg[0]); + } + } +} + +// Core function to process structs: =========================================== +void StructCore(MD5_CTX *context, const mxArray *V, mwSize nElem) +{ + // Sort field names alphabetically to avoid effects of teh order of fields. + const char *FieldName; + int nField, iField, FieldIndex; + mwSize iElem; + size_t FieldNameLen; + StringRec *FieldList; + + // Create list of field names and a handle array, which points to this list. + // Then sorting the handles allows to get the sorting index: + nField = mxGetNumberOfFields(V); + FieldList = (StringRec *) mxMalloc(nField * sizeof(StringRec)); + for (iField = 0; iField < nField; iField++) { + FieldList[iField].string = mxGetFieldNameByNumber(V, iField); + FieldList[iField].index = iField; + } + + // Sort the strings: + // (Sorting must not be stable, because the fieldnames are unique) + qsort(FieldList, nField, sizeof(StringRec), CompareStringRec); + + // Loop over fields: + for (iField = 0; iField < nField; iField++) { + // Encode field name: + FieldName = FieldList[iField].string; + FieldIndex = FieldList[iField].index; + FieldNameLen = STRING_LENGTH(FieldName, 63); + MD5_Update(context, (uchar_T *) FieldName, FieldNameLen); + + // Loop over struct array: + for (iElem = 0; iElem < nElem; iElem++) { + ArrayCore(context, mxGetFieldByNumber(V, iElem, FieldIndex)); + } + } + + // Release memory: + mxFree(FieldList); +} + +// Comparison for qsort(): ===================================================== +int CompareStringRec(const void *va, const void *vb) +{ + const StringRec *a = (const StringRec *)va, + *b = (const StringRec *)vb; + return strcmp(a->string, b->string); +} + +// Elementary array as byte stream: ============================================ +void ProcessBin(uchar_T *array, mwSize inputLen, uchar_T digest[16]) +{ + // Only the contents of the array is considered. Therefore double(0) and + // single([0,0]) reply the same hash. This works for numeric, char and logical + // arrays only, neitehr cells nor structs. + MD5_CTX context; + + MD5_Init(&context); + MD5_Update(&context, array, inputLen); + MD5_Final(digest, &context); +} + +// File as byte stream: ======================================================== +void ProcessFile(char *filename, uchar_T digest[16]) +{ + FILE *FID; + MD5_CTX context; + mwSize len; + + // Open the file in binary mode: + if ((FID = fopen(filename, "rb")) == NULL) { + ERROR3("MissFile", "Cannot open file: [%s]", filename); + } + + MD5_Init(&context); + while ((len = fread(buffer, 1, BUFFER_LEN, FID)) != 0) { + MD5_Update(&context, buffer, len); + } + MD5_Final(digest, &context); + + fclose(FID); +} + +// Output of 16 uchar_Ts as 32 character hexadecimals: =========================== +void ToHex(const uchar_T digest[16], char *output, int LowerCase) +{ + char *outputEnd, *Fmt; + const uchar_T *s = digest; + + Fmt = LowerCase ? "%02x" : "%02X"; + + for (outputEnd = output + 32; output < outputEnd; output += 2) { + sprintf(output, Fmt, *(s++)); + } +} + +// BASE64 encoded output: ====================================================== +void ToBase64(const uchar_T In[16], char *Out) +{ + // The base64 encoded string is shorter than the hex string. + // Needed length: ((len + 2) / 3 * 4) + 1, here fixed to 22+1 (trailing 0!). + static const uchar_T B64[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + int i; + char *p; + const uchar_T *s; + + p = Out; + s = In; + for (i = 0; i < 5; i++) { + *p++ = B64[(*s >> 2) & 0x3F]; + *p++ = B64[((*s & 0x3) << 4) | ((s[1] & 0xF0) >> 4)]; + *p++ = B64[((s[1] & 0xF) << 2) | ((s[2] & 0xC0) >> 6)]; + *p++ = B64[s[2] & 0x3F]; + s += 3; + } + + *p++ = B64[(*s >> 2) & 0x3F]; + *p++ = B64[((*s & 0x3) << 4)]; + *p = '\0'; +} + +// Main function: ============================================================== +void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) +{ + // Mex interface: + // - Define default values of optional arguments. + // - Forward input data to different calculators according to the input type. + // - Convert digest to output format. + + char *FileName, outString[33]; + uchar_T digest[16], *digestP, OutType = 'h'; + double *outP, *outEnd; + Method_t Method = BINARY_m; + mwSize nByte; + + // Check number of inputs and outputs: --------------------------------------- + if (nrhs == 0 || nrhs > 3) { + ERROR("BadNInput", "1 to 3 inputs required."); + } + if (nlhs > 1) { + ERROR("BadNOutput", "Too many output arguments."); + } + + // Check type of inputs: + if (mxIsChar(prhs[0])) { + Method = ASCII_m; // Default for CHAR arrays + } + + // Evaluate 1st character of 2nd input: + if (nrhs >= 2 && mxGetNumberOfElements(prhs[1]) > 0) { + if (mxIsChar(prhs[1]) == 0) { + ERROR("BadTypeInput2", "2nd input [Method] must be a string."); + } + + switch (*(uchar_T *) mxGetData(prhs[1])) { + case '8': + if (!mxIsChar(prhs[0])) { + WARN("NoASCII", "ASCII Mode ignored: Data is no CHAR."); + Method = BINARY_m; + } + break; + case 'b': + case 'B': Method = BINARY_m; break; + case 'a': + case 'A': Method = ARRAY_m; break; + case 'f': + case 'F': Method = FILE_m; break; + default: ERROR("BadInput2", "Mode not recognized."); + } + } + + // Output type - default: hex: + if (nrhs == 3 && !mxIsEmpty(prhs[2])) { + if (mxIsChar(prhs[2]) == 0) { + ERROR("BadTypeInput3", "3rd input must be a string."); + } + + OutType = *(uchar_T *) mxGetData(prhs[2]); // Just 1st character + } + + // Calculate check sum: ------------------------------------------------------ + switch (Method) { + case FILE_m: // Input is a file name: + if ((FileName = mxArrayToString(prhs[0])) == NULL) { + ERROR("StringFail", "Cannot get file name as string."); + } + ProcessFile(FileName, digest); + mxFree(FileName); + break; + + case ARRAY_m: // Type, dimensions and contents of the array: + ProcessArray(prhs[0], digest); + break; + + case ASCII_m: // Consider ASCII part of 16-bit mxChar only: + ProcessChar((mxChar *) mxGetData(prhs[0]), + mxGetNumberOfElements(prhs[0]), digest); + break; + + case BINARY_m: // Contents of the variable: + if (!(mxIsNumeric(prhs[0]) || mxIsChar(prhs[0]) || mxIsLogical(prhs[0])) + || mxIsComplex(prhs[0]) || mxIsSparse(prhs[0])) { + ERROR("BadDataType", + "Binary mode requires: non-sparse, real, numeric or CHAR data."); + } + nByte = mxGetNumberOfElements(prhs[0]) * mxGetElementSize(prhs[0]); + ProcessBin((uchar_T *) mxGetData(prhs[0]), nByte, digest); + break; + + default: + ERROR("BadSwitch", "Programming error: Unknown switch case?!"); + } + + // Create output: ------------------------------------------------------------ + switch (OutType) { + case 'H': + case 'h': // Hexadecimal upper/lower case: + ToHex(digest, outString, OutType == 'h'); + plhs[0] = mxCreateString(outString); + break; + + case 'D': + case 'd': // DOUBLE with integer values: + plhs[0] = mxCreateDoubleMatrix(1, 16, mxREAL); + outP = mxGetPr(plhs[0]); + digestP = digest; + for (outEnd = outP + 16; outP < outEnd; outP++) { + *outP = (double) *digestP++; + } + break; + + case 'B': + case 'b': // Base64: + ToBase64(digest, outString); // Locally implemented + plhs[0] = mxCreateString(outString); + break; + + case 'U': + case 'u': // UINT8: + plhs[0] = mxCreateNumericMatrix(1, 16, mxUINT8_CLASS, mxREAL); + memcpy(mxGetData(plhs[0]), digest, 16 * sizeof(uchar_T)); + break; + + default: + ERROR("BadOutputType", "Unknown output type."); + } + + return; +} diff --git a/GetMD5.m b/GetMD5.m new file mode 100644 index 0000000..a7bb749 --- /dev/null +++ b/GetMD5.m @@ -0,0 +1,114 @@ +function GetMD5(varargin) +% GetMD5 - 128 bit MD5 checksum: file, string, array, byte stream +% This function calculates a 128 bit checksum for arrays or files. +% Digest = GetMD5(Data, Mode, Format) +% INPUT: +% Data: File name or array. +% Mode: CHAR to declare the type of the 1st input. Not case-sensitive. +% 'File': Data is a file name as CHAR. +% '8Bit': If Data is a CHAR array, only the 8 bit ASCII part is +% used. Then the digest is the same as for a ASCII text +% file e.g. created by: FWRITE(FID, Data, 'uchar'). +% This is ignored if Data is not of type CHAR. +% 'Binary': The MD5 sum is obtained for the contents of Data. +% This works for numerical, CHAR and LOGICAL arrays. +% 'Array': Include the class and dimensions of Data in the MD5 +% sum. This can be applied for (nested) structs, cells +% and sparse arrays also. +% Optional. Default: '8Bit' for CHAR, 'Binary' otherwise. +% Format: CHAR, format of the output. Only the first character matters. +% The upper/lower case matters for 'hex' only. +% 'hex': [1 x 32] lowercase hexadecimal CHAR. +% 'HEX': [1 x 32] uppercase hexadecimal CHAR. +% 'double': [1 x 16] double vector with UINT8 values. +% 'uint8': [1 x 16] uint8 vector. +% 'base64': [1 x 22] CHAR, encoded to base 64 (A:Z,a:z,0:9,+,/). +% The CHAR is not padded to keep it short. +% Optional, default: 'hex'. +% +% OUTPUT: +% Digest: A 128 bit number is replied in the specified format. +% +% NOTE: +% * For sparse arrays, function handles, java and user-defined objects the +% M-file GetMD5_helper is called. +% * This replies the same MD5 hash as DataHash, but much faster. +% +% EXAMPLES: +% Three methods to get the MD5 of a file: +% 1. Direct file access (recommended): +% MD5 = GetMD5(which('GetMD5.m'), 'File') +% 2. Import the file to a CHAR array (no text mode for exact line breaks!): +% FID = fopen(which('GetMD5.m'), 'r'); +% S = fread(FID, inf, 'uchar=>char'); +% fclose(FID); +% MD5 = GetMD5(S, '8bit') +% 3. Import file as a byte stream: +% FID = fopen(which('GetMD5.m'), 'r'); +% S = fread(FID, inf, 'uint8=>uint8'); +% fclose(FID); +% MD5 = GetMD5(S, 'bin'); % 'bin' is optional +% +% Test data: +% GetMD5(char(0:511), '8bit', 'HEX') +% % => F5C8E3C31C044BAE0E65569560B54332 +% GetMD5(char(0:511), 'bin') +% % => 3484769D4F7EBB88BBE942BB924834CD +% GetMD5(char(0:511), 'array') +% % => b9a955ae730b25330d4f4ebb0a51e8f0 +% GetMD5('abc') % implicit '8bit' for CHAR +% % => 900150983cd24fb0d6963f7d28e17f72 +% +% COMPILATION: +% The C-Mex-file is compiled automatically, when this function is called the +% first time. +% See GetMD5.c for details or a manual compilation. +% +% Tested: Matlab/64 7.8, 7.13, 8.6, 9.1, Win7/64 +% Author: Jan Simon, Heidelberg, (C) 2009-2019 matlab.2010(a)n(MINUS)simon.de +% License: This program is derived from the RSA Data Security, Inc. +% MD5 Message Digest Algorithm, RFC 1321, R. Rivest, April 1992 +% This implementation is published under the BSD license. +% +% See also CalcCRC32, DataHash. +% +% For more checksum methods see: +% http://www.mathworks.com/matlabcentral/fileexchange/31272-datahash + +% $JRev: R5G V:038 Sum:yFie32x5VgiF Date:04-Mar-2019 16:20:44 $ +% $License: BSD (use/copy/change/redistribute on own risk, mention the author) $ +% $UnitTest: uTest_GetMD5 $ +% $File: Tools\GLFile\GetMD5.m $ +% History: +% 015: 15-Dec-2009 16:53, BUGFIX: UINT32 has 32 bits on 64 bit systems now. +% Thanks to Sebastiaan Breedveld! +% 026: 30-Jan-2015 00:12, 64 bit arrays larger than 2.1GB accepted. +% Successor of "CalcMD5". +% 031: 04-Jun-2016 22:26, Structs and cells are handled. +% 033: 03-May-2018 20:37, BUGFIX: Null pointer in data array. +% 035: 27-Aug-2018 00:52, Consider string class. + +% Dummy code, which calls the auto-compilation only: --------------------------- +% This M-function is not called, if the compiled MEX function is in the path. + +persistent FirstRun +if isempty(FirstRun) + fprintf('### %s: M-verion is running. Compiling MEX file...\n', mfilename); + + ok = InstallMex('GetMD5.c', 'uTest_GetMD5'); + if ok + FirstRun = false; + end + + try + dummy = GetMD5_helper([]); %#ok + catch ME + error(['JSimon:', mfilename, ':NoHelper'], ... + '### %s: GetMD5_Helper.m: %s', mfilename, ME.message); + end +else + error(['JSimon:', mfilename, ':MissMEX'], ... + 'Cannot find Mex file: %s', [mfilename, '.', mexext]); +end + +end diff --git a/GetMD5.mexa64 b/GetMD5.mexa64 new file mode 100755 index 0000000000000000000000000000000000000000..70b01afe0aefe0bb17d9e61f7e0da0ec21b99525 GIT binary patch literal 23064 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 literal 0 HcmV?d00001 diff --git a/GetMD5_helper.m b/GetMD5_helper.m new file mode 100644 index 0000000..3b2efd6 --- /dev/null +++ b/GetMD5_helper.m @@ -0,0 +1,110 @@ +function S = GetMD5_helper(V) +% GetMD5_helper: Convert non-elementary array types for GetMD5 +% The C-Mex function GetMD5 calls this function to obtain meaningful unique data +% for function handles, java or user-defined objects and sparse arrays. The +% applied processing can depend on the needs of the users, therefore it is +% implemented as an M-function, which is easier to modify than the C-code. +% +% INPUT: +% V: Array of any type, which is not handled in the C-Mex. +% OUTPUT: +% S: Array or struct containing elementary types only. +% The implementation migth be changed by the user! +% Default: +% - Sparse arrays: Struct containing the indices and values. +% - Function handle: The reply of FUNCTIONS and the size and date of the +% file. +% - User defined and java objects: V.hashCode if existing, else: struct(V). +% +% NOTE: +% For objects the function getByteStreamFromArray() might be exhaustive and +% efficient, but unfortunately it is not documented. +% +% Tested: Matlab/64 7.8, 7.13, 8.6, 9.1, Win7/64 +% Author: Jan Simon, Heidelberg, (C) 2016-2019 matlab.2010(a)n(MINUS)simon.de + +% $JRev: R5n V:013 Sum:I9OCbLsTN/AG Date:17-Feb-2019 19:01:00 $ +% $License: BSD (use/copy/change/redistribute on own risk, mention the author) $ +% $File: Tools\GLFile\GetMD5_helper.m $ +% History: +% 001: 28-Jun-2015 19:19, Helper for GetMD5. +% 012: 27-Aug-2018 01:38, String class considered. +% 013: 09-Feb-2019 18:08, ismethod(class(V),) to support R2018b. + +% Initialize: ================================================================== +% Do the work: ================================================================= +% isa(V, 'class') is surprisingly expensive. Get the class as string once: +classV = class(V); + +% The dimensions, number of dimensions and the name of the class is considered +% already in the MEX function! + +if strcmp(classV, 'function_handle') %#ok<*STISA> + % Please adjust the subfunction ConvertFuncHandles to your needs. + + % The Matlab version influences the conversion by FUNCTIONS: + % 1. The format of the struct replied FUNCTIONS is not fixed, + % 2. The full path of toolbox function e.g. for @mean differ. + S = functions(V); + + % Include modification file time and file size. Suggested by Aslak Grinsted: + if ~isempty(S.file) + d = dir(S.file); + if ~isempty(d) + S.filebytes = d.bytes; + S.filedate = d.datenum; + end + end + + % ALTERNATIVE: Use name and path. The part of the toolbox + % functions is replaced such that the hash for @mean does not depend on the + % Matlab version. + % Drawbacks: Anonymous functions, nested functions... + % funcStruct = functions(FuncH); + % funcfile = strrep(funcStruct.file, matlabroot, ''); + % S = uint8([funcStruct.function, ' ', funcfile]); + + % Finally I'm afraid there is no unique method to get a hash for a function + % handle. Please adjust this conversion to your needs. + +elseif (isobject(V) || isjava(V)) && ismethod(classV, 'hashCode') % =========== + % Java or user-defined objects might have a hash function: + S = char(V.hashCode); + +elseif issparse(V) % ========================================================== + % Create struct with indices and non-zero values: + [S.Index1, S.Index2, S.Value] = find(V); + +elseif strcmp(classV, 'string') % Since R2016b: =============================== + % Convert all strings to UINT8 byte stream including the dimensions: + classUint8 = uint8([117, 105, 110, 116, 49, 54]); + C = cell(1, numel(V)); + for iC = 1:numel(V) + % Engine = CoreHash(uint16(Data{iS}), Engine); + aString = uint16(V{iC}); + C{iC} = [classUint8, ... + typecast(uint64([ndims(aString), size(aString)]), 'uint8'), ... + typecast(uint16(aString), 'uint8')]; + end + S = [uint8([115, 116, 114, 105, 110, 103]), ... % 'string' + typecast(uint64([ndims(V), size(V)]), 'uint8'), ... + cat(2, C{:})]; + +else % Most likely this is a user-defined object: ============================= + try % Perhaps a direct conversion is implemented: + S = uint8(V); + + % Matt Raum had this excellent idea - unfortunately this function is + % undocumented and might not be supported in te future: + % S = getByteStreamFromArray(DataObj); + + catch ME % Or perhaps this is better: + fprintf(2, ['### %s: Convert object to struct as fallback.', char(10), ... + ' %s\n'], ME.message); + WarnS = warning('off', 'MATLAB:structOnObject'); + S = struct(V); + warning(WarnS); + end +end + +% return; diff --git a/InstallMex.m b/InstallMex.m new file mode 100644 index 0000000..7c13880 --- /dev/null +++ b/InstallMex.m @@ -0,0 +1,330 @@ +function Ok = InstallMex(SourceFile, varargin) +% INSTALLMEX - Compile and install Mex file +% The C, C++ or FORTRAN mex file is compiled and additional installation +% routines are started. Advanced users can call MEX() manually instead, but some +% beginners are overwhelmed by instructions for a compilation sometimes. +% Therefore this function can be called automatically from an M-function, when +% the compiled Mex-Function does not exist already. +% +% Ok = InstallMex(SourceFile, ...) +% INPUT: +% SourceFile: Name of the source file, with or without absolute or partial +% path. The default extension '.c' is appended on demand. +% Optional arguments in arbitrary order: +% Function name: Function is started after compiling, e.g. a unit-test. +% Cell string: Additional arguments for the compilation, e.g. libraries. +% '-debug': Enable debug mode. +% '-force32': Use the compatibleArrayDims flag under 64 bit Matlab. +% '-replace': Overwrite existing mex file without confirmation. +% +% OUTPUT: +% Ok: Logical flag, TRUE if compilation was successful. Optional. +% +% COMPATIBILITY: +% - A compiler must be installed and setup before: mex -setup +% - For Linux and MacOS the C99 style is enabled for C-files. +% - The optimization flag -O is set. +% - Defined compiler directives: +% * MATLABVER: is the current version, e.g. 708 for v7.8. +% * _LITTLE_ENDIAN or _BIG_ENDIAN: according to processor type +% * HAS_HG2: Defined for Matlab >= R2014b with HG2 graphics. +% * -R2017b or -largeArrayDims for 64 bit addressing and C Matrix API +% -R2018a for C Data API (this is set, when the string "[R2018a API]" +% appears anywhere inside the source file. +% +% EXAMPLES: +% Compile func1.c with LAPACK libraries: +% InstallMex('func1', {'libmwlapack.lib', 'libmwblas.lib'}) +% Compile func2.cpp, enable debugging and call a test function: +% InstallMex('func2.cpp', '-debug', 'Test_func2'); +% These commands can be appended after the help section of an M-file, when the +% compilation should be started automatically, if the compiled MEX is not found. +% +% NOTES: +% Suggestions for improvements and comments are welcome! +% Feel free to add this function to your FEX submissions, when you change the +% URL in the variable "Precompiled" accordingly. +% +% Tested: Matlab/64 7.8, 7.13, 8.6, 9.1, Win7/64 +% Author: Jan Simon, Heidelberg, (C) 2012-2019 matlab.2010(a)n(MINUS)simon.de + +% $JRev: R5L V:037 Sum:jndyPgDqNKoN Date:17-Feb-2019 19:01:00 $ +% $License: BSD (use/copy/change/redistribute on own risk, mention the author) $ +% $File: Tools\GLSource\InstallMex.m $ +% History: +% 001: 27-Jul-2012 09:06, First version. +% 005: 29-Jul-2012 17:11, Run the unit-test instead of showing a link only. +% 006: 11-Aug-2012 23:59, Inputs are accepted in free order. +% 020: 30-Dec-2013 01:48, Show a question dialog if mex is existing already. +% 027: 08-Mar-2015 22:15, Define _LITTLE_ENDIAN / _BIG_ENDIAN. +% 028: 22-Aug-2015 19:16, Define HAS_HG2 for Matlab >= 2014b. +% 029: 24-Dec-2015 17:46, CATCH MException: No Matlab6.5 support anymore. +% 032: 24-Apr-2016 17:45, Bugs fixed: "cmd" instead of "cmdStr". +% 037: 09-Feb-2019 17:49, -R2017b and -R2018a. + +% No warnings in Matlab versions without String support: +%#ok<*ISCLSTR> +%#ok<*STREMP> + +% Initialize: ================================================================== +% Global Interface: ------------------------------------------------------------ +% URL to file or folder containing pre-compiled files, or the empty string if +% pre-compiled files are not offered: +% ### START: ADJUST TO USER NEEDS +Precompiled = 'http://www.n-simon.de/mex'; +% ### END + +% Initial values: -------------------------------------------------------------- +Ok = false; +bakCD = cd; +matlabV = [100, 1] * sscanf(version, '%d.%d', 2); % Matlab version +[Arch, MaxSize, Endian] = computer; %#ok + +hasHG2 = (matlabV >= 804); % R2014b +hasLargeDims = (matlabV >= 705) & any(strfind(Arch, '64')); % R2007b & 64bit +isLittleEndian = strncmpi(Endian, 'L', 1); +hasR2018aAPI = (matlabV >= 904); % R2018a + +% Program Interface: ----------------------------------------------------------- +% Parse inputs: +Param = {}; +UnitTestFcn = ''; +doDebug = false; +debugFlag = {}; +force32 = false; +replace = false; + +% First input is the name of the source file: +if ~ischar(SourceFile) + error_L('BadTypeInput1', '1st input must be a string.'); +end + +% Additional inputs are identified by their type: +% String: unit-test function or the flag to enable debugging. +% Cell string: additional parameters for the MEX command +for iArg = 1:numel(varargin) + Arg = varargin{iArg}; + if ischar(Arg) + if strcmpi(Arg, '-debug') + doDebug = true; + debugFlag = {'-v'}; + elseif strcmpi(Arg, '-force32') + force32 = true; + elseif strcmpi(Arg, '-replace') + replace = true; + elseif exist(Arg, 'file') == 2 + UnitTestFcn = Arg; + else + error_L('MissFile', 'Unknown string or missing file: %s', Arg); + end + elseif iscellstr(Arg) % As row cell: + Param = Arg(:).'; + else + error_L('BadInputType', 'Bad type of input.'); + end +end + +% User Interface: -------------------------------------------------------------- +hasHRef = usejava('jvm'); % Hyper-links in the command window? + +% Do the work: ================================================================= +% Search the source file, solve partial or relative path, get the real +% upper/lower case: +[dummy, dummy, Ext] = fileparts(SourceFile); %#ok +if isempty(Ext) + SourceFile = [SourceFile, '.c']; +end + +fullSource = which(SourceFile); +if isempty(fullSource) + error_L('NoSource', 'Cannot find the source file: %s', SourceFile); +end +[SourcePath, SourceName, Ext] = fileparts(fullSource); +Source = [SourceName, Ext]; + +% Consider output name and outputdir: +index = find(strcmpi(Param, '-output'), 1, 'last'); +if isempty(index) + mexName = SourceName; +else + [dummy, mexName] = fileparts(Param{index + 1}); %#ok +end +mexFile = [mexName, '.', mexext]; + +index = find(strcmpi(Param, '-outdir'), 1, 'last'); +if isempty(index) + mexPath = SourcePath; +else + mexPath = Param{index + 1}; +end + +fprintf('== Compile: %s\n', fullfile(SourcePath, Source)); + +% Check if the compiled file is existing already: +whichMex = which(mexFile); +if ~isempty(whichMex) + fprintf(' Existing already: %s\n', whichMex); + + if ~replace + % Ask the user if a new compilation is wanted: + QuestReply = questdlg({ ... + ['\bf', mfilename, ': ', TeXFirm(SourceName), '\rm'], ... + '', 'The function is existing already:', ... + [' ', TeXFirm(whichMex)], '', ... + 'Do you want to compile it here:', ... + [' ', TeXFirm(fullfile(mexPath, mexFile))], ''}, ... + mfilename, 'Compile', 'Cancel', ... + struct('Default', 'Cancel', 'Interpreter', 'tex')); + + % User does not want to recompile: + if strcmp(QuestReply, 'Cancel') + if nargout + Ok = false; + end + return; + end + end + fprintf(' Recompile in: %s\n\n', mexPath); +end + +if ~ispc && strcmpi(Ext, '.c') + % C99 for the GCC and XCode compilers. + % Note: 'CFLAGS="\$CFLAGS -std=c99"' must be separated to 2 strings!!! + Opts = {'-O', 'CFLAGS="\$CFLAGS', '-std=c99"'}; +else + Opts = {'-O'}; +end + +% Define endianess directive: +if isLittleEndian + Opts = cat(2, Opts, {'-D_LITTLE_ENDIAN'}); +else % Does Matlab run on any big endian machine currently?! + Opts = cat(2, Opts, {'-D_BIG_ENDIAN'}); +end + +% Define the new HG2 graphic handles: +if hasHG2 + Opts = cat(2, Opts, {'-DHAS_HG2'}); +end + +% Decide for the old R2017b or new R2018a API: +matlabVDef = {sprintf('-DMATLABVER=%d', matlabV)}; +if hasR2018aAPI + % If the C file contains the string " mex -R2018a " anywhere, the modern API + % is enabled: + tmp = fileread(fullSource); + if isempty(strfind(tmp, '[R2018a API]')) + compatibleAPI = '-R2017b'; + else + compatibleAPI = '-R2018a'; + end +elseif hasLargeDims && ~force32 + % Large array dimensions under 64 bit, possible since R2007b: + compatibleAPI = '-largeArrayDims'; +else + % 32 bit addressing, possible since R2007b: + % Equivalent: -DMX_COMPAT_32 + compatibleAPI = '-compatibleArrayDims'; +end +Opts = cat(2, Opts, {compatibleAPI}); + +% Compile: --------------------------------------------------------------------- +% Display the compilation command: +Opts = cat(1, Opts(:), debugFlag, matlabVDef, Param(:), {Source}); +cmdStr = ['mex', sprintf(' %s', Opts{:})]; +fprintf('%s\n\n', cmdStr); + +cd(SourcePath); +try % Start the compilation: + mex(Opts{:}); + compiled = true; + fprintf('Compiled:\n %s\n', which(mexFile)); + +catch ME % Compilation failed - MException fails in Matlab 6.5! + compiled = false; + fprintf(2, '\n*** Compilation failed:\n%s\n\n', ME.message); + fprintf('Matlab version: %s\n', version); + if ~doDebug % Compile again in debug mode if not done already: + fprintf('Compile with debug flag to get details:\n'); + try + mex(Opts{:}, '-v'); + catch % Empty - it is known already that it fails + end + end + + % Show commands for manual compilation and download pre-compiled files: + fprintf('\n== The compilation failed! Possible solutions:\n'); + fprintf(' * Is a compiler installed and set up properly by: mex -setup?\n'); + fprintf(' * Try to compile manually:\n cd(''%s'')\n', SourcePath); + fprintf(' %s -v\n', cmdStr); + fprintf(' * Or download the pre-compiled file %s:\n', mexFile); + if ~isempty(Precompiled) + if hasHRef + fprintf( ... + ' %s\n', ... + Precompiled, mexFile, Precompiled); + else % No hyper-references in the command window without Java: + fprintf(' %s\n', Precompiled); + end + end + fprintf(' * Please send this report to the author.\n'); +end + +% Restore original directory and check precedence: ----------------------------- +cd(bakCD); +if compiled + allWhich = which(mexName, '-all'); + if ~strcmpi(allWhich{1}, fullfile(mexPath, mexFile)) + Spec = sprintf(' %%-%ds ', max(cellfun('length', allWhich))); + fprintf(2, '\n*** Failed: Compiled function is shadowed:\n'); + fprintf(2, [Spec, '*USED*\n'], allWhich{1}); + fprintf(2, [Spec, '*SHADOWED*\n'], allWhich{2:end}); + + compiled = false; + end + + allWhichMex = which(mexFile, '-all'); + if length(allWhichMex) > 1 + fprintf(2, '\n::: Multiple instances of compiled file:\n'); + fprintf(2, ' %s\n', allWhich{:}); + end +end + +% Run the unit-test: ----------------------------------------------------------- +if ~isempty(UnitTestFcn) && compiled + fprintf('\n\n== Post processing:\n'); + [dum, UnitTestName] = fileparts(UnitTestFcn); %#ok % Remove extension + if ~isempty(which(UnitTestName)) + fprintf(' Call: %s\n\n', UnitTestName); + feval(UnitTestName); + else + fprintf(2, '??? Cannot find function: %s\n', UnitTestFcn); + end +end + +% Return success of compilation: ----------------------------------------------- +if nargout >= 1 + Ok = compiled; +end +if compiled + fprintf('\n== %s: ready.\n', mfilename); +else + fprintf('\n== %s: failed.\n', mfilename); +end + +% end + +% ****************************************************************************** +function error_L(ID, Msg, varargin) +% Automatic error ID and mfilename in the message: +error(['JSimon:', mfilename, ':', ID], ... + ['*** %s: ', Msg], mfilename, varargin{:}); + +% end + +% ****************************************************************************** +function S = TeXFirm(S) +% Escape special characters for the TeX interpreter: +S = strrep(strrep(S, '\', '\\'), '_', '\_'); + +% end diff --git a/distribution/mexa64/mym.mexa64 b/distribution/mexa64/mym.mexa64 index 543703817672c51680d7ec1626b4268a1ddaaca9..c8fee3647e9656061214e89085e43aa67926e98e 100755 GIT binary patch literal 48560 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 W)viT*vP1coA5#=(^eNB>H2xn>FUxNL literal 48192 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 BuP^`r diff --git a/libmx.so b/libmx.so new file mode 100755 index 0000000000000000000000000000000000000000..7237cfc2ce50f69594eae3e02f79ce44385ee325 GIT binary patch literal 1983320 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