├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE ├── README.md ├── docs ├── _c74_common.css ├── _c74_common.xml ├── _c74_common.xsl ├── _c74_compat.html ├── _c74_platform.xsl ├── _c74_ref.xsl ├── _c74_ref_common.xsl ├── _c74_vig.xsl ├── _c74_vig_common.xsl ├── test.assert.maxref.xml ├── test.equals.maxref.xml ├── test.error.maxref.xml ├── test.log.maxref.xml ├── test.sample~.maxref.xml ├── test.string.equals.maxref.xml └── test.terminate.maxref.xml ├── help ├── oscar-extended.maxhelp ├── oscar.maxhelp ├── test.assert.maxhelp ├── test.equals.maxhelp ├── test.error.maxhelp ├── test.log.maxhelp ├── test.sample~.maxhelp ├── test.string.equals.maxhelp └── test.terminate.maxhelp ├── init └── testpackage-init.txt ├── interfaces └── testpackage.db.json ├── misc ├── max-test-config-example.json ├── test.error.maxpat ├── test.string.equals.js └── test.string.equals.maxpat ├── package-info.json.in ├── patchers ├── 2087-bitxor~.maxtest.maxpat ├── 2249-dict-syntax-spaces.maxtest.maxpat ├── 2775-scale~.maxtest.maxpat ├── 2779-3314-allpass~.impulse-response.aif ├── 2779-3314-allpass~.maxtest.maxpat ├── 2859-dict-singles.maxtest.maxpat ├── 4505-dict-notification-js.maxtest.maxzip ├── 4521-dict-contains.maxtest.maxpat ├── 4863-buffer~_duration.maxtest.maxpat ├── console-has-error.maxtest.maxpat └── lib │ ├── CheckConsoleClear.maxpat │ └── CheckConsoleHasError.maxpat ├── ruby ├── rosc │ ├── AUTHORS │ ├── ChangeLog │ ├── GPL.txt │ ├── LICENSE │ ├── README │ ├── Rakefile │ ├── TODO │ ├── examples │ │ └── readme.rb │ ├── lib │ │ ├── osc.rb │ │ └── osc │ │ │ ├── pattern.rb │ │ │ ├── server.rb │ │ │ ├── transport.rb │ │ │ ├── udp.rb │ │ │ └── udp_server_with_count.rb │ ├── setup.rb │ └── test │ │ └── test_osc.rb └── test.rb ├── source └── projects │ └── oscar │ ├── CMakeLists.txt │ ├── ext_test.cpp │ ├── ext_test.h │ ├── oscar.c │ ├── oscar.h │ ├── test.assert.cpp │ ├── test.db.cpp │ ├── test.equals.cpp │ ├── test.log.cpp │ ├── test.master.c │ ├── test.port.cpp │ ├── test.runner.c │ ├── test.sample~.cpp │ ├── test.terminate.cpp │ └── test.unit.c └── zip-it.sh /.gitignore: -------------------------------------------------------------------------------- 1 | sysbuild 2 | *.sdf 3 | *.suo 4 | *.sln 5 | *.opensdf 6 | log.txt 7 | externals 8 | extensions 9 | support 10 | build 11 | *.o 12 | *.dylib 13 | tmp 14 | .DS_Store 15 | 16 | package-info.json 17 | 18 | 19 | # User Data Files 20 | *.db3 21 | xcuserdata 22 | project.xcworkspace 23 | .vs 24 | intermediate 25 | *.vcxproj.user 26 | misc/max-test-config.json 27 | .dsp_cache 28 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "source/max-sdk-base"] 2 | path = source/max-sdk-base 3 | url = https://github.com/cycling74/max-sdk-base 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(Oscar) 2 | 3 | cmake_minimum_required(VERSION 3.19) 4 | 5 | string(REGEX REPLACE "(.*)/" "" THIS_PACKAGE_NAME "${CMAKE_CURRENT_SOURCE_DIR}") 6 | 7 | set(CMAKE_OSX_ARCHITECTURES x86_64;arm64) 8 | 9 | 10 | if (${CMAKE_GENERATOR} MATCHES "Xcode") 11 | if (${XCODE_VERSION} VERSION_LESS 10) 12 | message(STATUS "Xcode 10 or higher is required. Please install from the Mac App Store.") 13 | return () 14 | endif () 15 | endif () 16 | 17 | 18 | # Fetch the correct version of the min-api 19 | message(STATUS "Updating Git Submodules") 20 | execute_process( 21 | COMMAND git submodule update --init --recursive 22 | WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" 23 | ) 24 | 25 | 26 | # Misc setup and subroutines 27 | include(${CMAKE_CURRENT_SOURCE_DIR}/source/max-sdk-base/script/max-package.cmake) 28 | 29 | 30 | # Generate a project for every folder in the "source/projects" folder 31 | SUBDIRLIST(PROJECT_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/source/projects) 32 | foreach (project_dir ${PROJECT_DIRS}) 33 | if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/source/projects/${project_dir}/CMakeLists.txt") 34 | message("Generating: ${project_dir}") 35 | add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/source/projects/${project_dir}) 36 | endif () 37 | endforeach () 38 | 39 | # Comment the line below if you want automatic cmake regneration enabled 40 | set(CMAKE_SUPPRESS_REGENERATION true) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Cycling '74 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # max-test 2 | Automated Test Harness for Max 3 | 4 | This package provides a set of simple tools with which to instrument patchers to verify expected behavior. Patchers that are instrumented as "test patchers" assist in fast identification of bugs, of the expectations of the patcher's author, and fast verification once a bug is addressed. Furthermore, instrumented test patchers can be evaluated as part of an automated system on multiple architectures and platforms. 5 | 6 | A video introduction is available at https://www.youtube.com/watch?v=l_3W4tZcgXI 7 | 8 | ## Construction of a Test 9 | 10 | A test patcher **must** meet the following criteria: 11 | 12 | * File name must end in .maxtest.maxpat (for patchers) or .maxtest.maxzip (for projects). 13 | * The patcher must start itself. Typically this is done with a loadbang object. 14 | * The test **must** terminate itself when complete. Failing to do so could hang the automated system from proceeding. A test is terminated by sending a bang to the test.terminate object. 15 | * The test must contain 1 or more test.assert objects. These objects define the expected results. 16 | 17 | 18 | ## Examples 19 | 20 | The 'patchers' folder in this package contains some example test patchers for reference. Here are the examples with some associated topics: 21 | 22 | * **2087-bitxor~** : use test.sample~ instead of snapshot~ for more predictable behavior in a test setting. Use test.equals for comparing floats instead of ==. Use right-to-left ordering and the last part of the test sequence to terminate the test rather than relying on asynchonous methods such as delays or defers. 23 | * **2249-dict-syntax-space** : simple patcher (no audio) for checking that args to dict are interpretted correctly. 24 | * **2779-3313-allpass~** : comparison of a complex impulse response produced by a filter by recording it into a buffer~, then comparing that to a reference buffer~ that contains the correct impulse response. Requires audio to run for at least 1 second in order to get the impulse response. 25 | * **2859-dict-singles** : string comparison where the strings are composed of multiple-lines. Uses the test.string.equals object because otherwise the test, authored on a Mac, will fail on Windows due to line-endings being different between the results and reference strings. 26 | * **4505-dict-notification-js** : example of a test as a Max project. 27 | * **4521-dict-contains** : trapping expected (or unexpected) errors to the Max window. 28 | * **4863-buffer~-duration** : simply checking accumulated error at the end instead of a bunch of individual checks. 29 | 30 | 31 | ## Running the Tests 32 | 33 | You can open a test patcher/project at any time and view it visually to inspect the results. 34 | 35 | To run the tests in an automated fashion from with Max, see the 'oscar.maxhelp' patcher in the 'help' folder. To access the results of the tests, see the section below on test results. 36 | 37 | 38 | ## Fully Automated Testing with Ruby 39 | 40 | You can communicate with Max remotely to fully automate the running of test patchers and log the results. An example for how to do this is provided as a Ruby script that can be expanded or retooled to suit your needs. 41 | 42 | ### Remote Communication 43 | 44 | Communication with uses Open Sound Control communicated via UDP. Internal to Max this is implemented using the udpsend and udpreceive objects. By default the oscar extension does not have remote communication enabled. 45 | 46 | To enable this remote communication you must set it by creating a configuration file in the `max-test/misc` folder called `max-test-config.json` -- the contents of which should look like this: 47 | 48 | { 49 | "port-send" : 4792, 50 | "port-listen" : 4791 51 | } 52 | 53 | The ports may be changed to suit your needs. Any changes in the ports used by oscar will also require that you change the Ruby script to use the same ports for communication. 54 | 55 | ### Running the Ruby Script 56 | 57 | Having configured Max to enable remote communication, now start a Terminal/Console session and cd into the `max-test/ruby` folder. Now you can run the script by typing `ruby test.rb` to get some simple assistance regarding the arguments. 58 | 59 | To actually start the tests you will need to provide the path to your Max application folder, e.g. 60 | `ruby test.rb "/Applications/Max 6.1"` 61 | 62 | The process may take a few minutes. First Max is launched. Then two-way communication is established. Now we must wait for Max's file database to complete so that all of the tests in the searchpath can be found. Finally, the tests will begin running. When the tests are done, the results will be summarized on the console. 63 | 64 | 65 | ## Automated Test Results 66 | 67 | The results of automated tests are stored in a SQLite database. The database is located in the `max-test` folder and named according to the path to the Max application used to run the tests. You can use any SQLite client application to read the results. On the Mac you can also use the built-in `sqlite3` program in the Terminal. 68 | 69 | The Ruby script provides a summary of the test results by reading the database. 70 | -------------------------------------------------------------------------------- /docs/_c74_common.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /docs/_c74_compat.html: -------------------------------------------------------------------------------- 1 | 19 | 20 | 21 | 23 | 24 | 25 | 26 | 27 | Compatibility Notice 28 | 29 | 30 | 31 | 32 | 33 |

34 | If you are reading this, you are running Max 5 on OSX 10.4.10 or below. Unfortunately, due to bugs in Apple's web browser support in these versions of the operating system, the Max 5 in-application documentation won't display properly.

35 | 36 |

37 | We recommend updating to OSX 10.4.11 and apologize for any inconvenience this may cause.

38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /docs/_c74_platform.xsl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 21 | 22 | 23 | 24 | 25 | 1 26 | 27 | 28 | 1 29 | 0 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /docs/_c74_ref.xsl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /docs/_c74_vig.xsl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /docs/test.assert.maxref.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Evaluate the sucess of a test 6 | 7 | test.assert takes a 1 (success) or 0 (fail) as input to evaluate the success of an operation in a test patcher. 8 | 9 | 10 | 11 | 12 | 13 | Cycling '74 14 | Testing 15 | 16 | 17 | 18 | 19 | 20 | 21 | Name of assertion 22 | 23 | Every assertion in a test patcher should be named with an argument. 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 1/0 to indicate pass/fail 34 | 35 | If no input is received, the assertion is deemed to have failed. 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | Optional tags to assist searching 45 | 46 | Tags can be used to assist with searching the database of test results after automated testing has been executed. 47 | Tags are optional and are space-delimited. 48 | You can define up to 16 tags. 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /docs/test.equals.maxref.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Like the == object but better for floats 6 | 7 | test.equals is somewhat like == but better suited for comparing floats. 8 | 9 | 10 | 11 | 12 | 13 | Cycling '74 14 | Testing 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | Number to be compared 27 | 28 | Number in the right inlet sets the reference against which to compare. 29 | Number in the left inlet compares and against the reference and sends a true/false (1 or 0) to the outlet. 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | Amount of error to accept 39 | 40 | The amount of error to accept when comparing two floating-point numbers. 41 | The tolerance defines the number of floating-point representations in either direction of the operand to consider as valid when determining equality. 42 | 43 | 44 | 45 | 46 | Force comparisons to 32-bit resolution 47 | 48 | Comparisons will be done, by default, at 64-bit (double-precision) when running Max in 64-bits. 49 | When run in 32-bit mode, comparisons will be made with 32-bit (single-precision). 50 | If comparisons need to be done with 32-bit precision, even when Max is running in 64-bits, set this flag to 1. 51 | For example, with Max 6.1 running in 64-bits the buffer~ object is still internally representing samples at 32-bits and any values coming from buffer~ will be subject to 32-bit roundoff errors. 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /docs/test.error.maxref.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Trap errors coming from the Max window 6 | 7 | test.error is an abstraction around the error object. 8 | 9 | 10 | 11 | 12 | 13 | Cycling '74 14 | Testing 15 | 16 | 17 | 18 | 19 | 20 | 21 | Report true on error 22 | 23 | If non-zero, the result of an error will be true. 24 | Otherwise, the result of an error will be false (a non-error will be true). 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Arm the object to listen for errors 34 | 35 | Arm the object to listen for errors posted to the Max window. 36 | It will stop listening the next time the low-priority queue is serviced. 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /docs/test.log.maxref.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Log messages/data in a test 6 | 7 | test.log is like the print object, except that the input will be printed to the test result database rather than just the Max window. 8 | 9 | 10 | 11 | 12 | 13 | Cycling '74 14 | Testing 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | messages to log in the test results 27 | 28 | Messages to log in the test results. All messages will be automatically time-stamped in the database. 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /docs/test.sample~.maxref.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Get samples from an audio signal 6 | 7 | test.sample~ is somewhat like snapshot~ but better suited to the testing environment. 8 | 9 | 10 | 11 | 12 | 13 | Cycling '74 14 | Testing 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | Send collected samples to the outlet 26 | 27 | Send collected samples to the outlet as floats. 28 | Resets collection so that it can begin again. 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | Offset into the vector at which to grab samples 37 | 38 | 39 | Number of vectors to wait before grabbing samples 40 | 41 | 42 | Number of samples to grab 43 | 44 | 45 | Automatically 'bang' when audio is turned-on the first time. 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /docs/test.string.equals.maxref.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Compare two strings for equality 6 | 7 | test.string.equals compares two strings for equality, but ignores differences between Mac line-endings and Windows line-endings. 8 | 9 | 10 | 11 | 12 | 13 | Cycling '74 14 | Testing 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | String to be compared 27 | 28 | String in the right inlet sets the reference against which to compare. 29 | String in the left inlet compares and against the reference and sends a true/false (1 or 0) to the outlet. 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /docs/test.terminate.maxref.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | End a test 6 | 7 | test.terminate takes a bang to indicate that a test is complete and should be terminated. 8 | 9 | 10 | 11 | 12 | 13 | Cycling '74 14 | Testing 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | terminate a test 27 | 28 | Note: DSP will be turned-off as a part of terminating the test. 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /help/oscar-extended.maxhelp: -------------------------------------------------------------------------------- 1 | { 2 | "patcher" : { 3 | "fileversion" : 1, 4 | "appversion" : { 5 | "major" : 7, 6 | "minor" : 0, 7 | "revision" : 0, 8 | "architecture" : "x86" 9 | } 10 | , 11 | "rect" : [ 100.0, 100.0, 693.0, 652.0 ], 12 | "bglocked" : 0, 13 | "openinpresentation" : 0, 14 | "default_fontsize" : 13.0, 15 | "default_fontface" : 0, 16 | "default_fontname" : "Helvetica Neue Light", 17 | "gridonopen" : 0, 18 | "gridsize" : [ 5.0, 5.0 ], 19 | "gridsnaponopen" : 0, 20 | "statusbarvisible" : 2, 21 | "toolbarvisible" : 1, 22 | "boxanimatetime" : 200, 23 | "imprint" : 0, 24 | "enablehscroll" : 1, 25 | "enablevscroll" : 1, 26 | "devicewidth" : 0.0, 27 | "description" : "", 28 | "digest" : "", 29 | "tags" : "", 30 | "boxes" : [ { 31 | "box" : { 32 | "fontname" : "Helvetica Neue Light", 33 | "fontsize" : 13.0, 34 | "frgb" : 0.0, 35 | "id" : "obj-11", 36 | "maxclass" : "comment", 37 | "numinlets" : 1, 38 | "numoutlets" : 0, 39 | "patching_rect" : [ 530.0, 475.0, 150.0, 22.0 ], 40 | "text" : "Run an integration test" 41 | } 42 | 43 | } 44 | , { 45 | "box" : { 46 | "fontname" : "Helvetica Neue Light", 47 | "fontsize" : 13.0, 48 | "id" : "obj-8", 49 | "linecount" : 2, 50 | "maxclass" : "message", 51 | "numinlets" : 2, 52 | "numoutlets" : 1, 53 | "outlettype" : [ "" ], 54 | "patching_rect" : [ 240.0, 470.0, 279.0, 35.0 ], 55 | "text" : ";\rtest.master run 4505-dict-notification-js.maxtest" 56 | } 57 | 58 | } 59 | , { 60 | "box" : { 61 | "fontname" : "Helvetica Neue Light", 62 | "fontsize" : 13.0, 63 | "frgb" : 0.0, 64 | "id" : "obj-18", 65 | "linecount" : 3, 66 | "maxclass" : "comment", 67 | "numinlets" : 1, 68 | "numoutlets" : 0, 69 | "patching_rect" : [ 415.0, 355.0, 150.0, 53.0 ], 70 | "text" : "Or... run a specific test on all objects with that test defined." 71 | } 72 | 73 | } 74 | , { 75 | "box" : { 76 | "fontname" : "Helvetica Neue Light", 77 | "fontsize" : 13.0, 78 | "frgb" : 0.0, 79 | "id" : "obj-17", 80 | "linecount" : 2, 81 | "maxclass" : "comment", 82 | "numinlets" : 1, 83 | "numoutlets" : 0, 84 | "patching_rect" : [ 415.0, 240.0, 153.0, 37.0 ], 85 | "text" : "Or run a specific test (list) on a named object" 86 | } 87 | 88 | } 89 | , { 90 | "box" : { 91 | "fontname" : "Helvetica Neue Light", 92 | "fontsize" : 13.0, 93 | "frgb" : 0.0, 94 | "id" : "obj-15", 95 | "linecount" : 2, 96 | "maxclass" : "comment", 97 | "numinlets" : 1, 98 | "numoutlets" : 0, 99 | "patching_rect" : [ 390.0, 150.0, 150.0, 37.0 ], 100 | "text" : "Run all tests for another named object (round)" 101 | } 102 | 103 | } 104 | , { 105 | "box" : { 106 | "fontname" : "Helvetica Neue Light", 107 | "fontsize" : 13.0, 108 | "frgb" : 0.0, 109 | "id" : "obj-14", 110 | "linecount" : 2, 111 | "maxclass" : "comment", 112 | "numinlets" : 1, 113 | "numoutlets" : 0, 114 | "patching_rect" : [ 375.0, 105.0, 152.0, 37.0 ], 115 | "text" : "Run all tests for a named object (zl)" 116 | } 117 | 118 | } 119 | , { 120 | "box" : { 121 | "fontname" : "Helvetica Neue Light", 122 | "fontsize" : 13.0, 123 | "frgb" : 0.0, 124 | "id" : "obj-12", 125 | "linecount" : 4, 126 | "maxclass" : "comment", 127 | "numinlets" : 1, 128 | "numoutlets" : 0, 129 | "patching_rect" : [ 360.0, 570.0, 150.0, 68.0 ], 130 | "text" : "Run all tests for all objects (uses the database to find all objects)" 131 | } 132 | 133 | } 134 | , { 135 | "box" : { 136 | "fontname" : "Helvetica Neue Light", 137 | "fontsize" : 13.0, 138 | "frgb" : 0.0, 139 | "id" : "obj-10", 140 | "linecount" : 3, 141 | "maxclass" : "comment", 142 | "numinlets" : 1, 143 | "numoutlets" : 0, 144 | "patching_rect" : [ 40.0, 585.0, 150.0, 53.0 ], 145 | "text" : "test results are in a database in the max application folder" 146 | } 147 | 148 | } 149 | , { 150 | "box" : { 151 | "fontname" : "Helvetica Neue Light", 152 | "fontsize" : 13.0, 153 | "id" : "obj-9", 154 | "maxclass" : "message", 155 | "numinlets" : 2, 156 | "numoutlets" : 1, 157 | "outlettype" : [ "" ], 158 | "patching_rect" : [ 45.0, 20.0, 40.0, 20.0 ], 159 | "text" : "open" 160 | } 161 | 162 | } 163 | , { 164 | "box" : { 165 | "fontname" : "Helvetica Neue Light", 166 | "fontsize" : 13.0, 167 | "id" : "obj-1", 168 | "maxclass" : "newobj", 169 | "numinlets" : 1, 170 | "numoutlets" : 3, 171 | "outlettype" : [ "", "bang", "int" ], 172 | "patching_rect" : [ 45.0, 45.0, 97.0, 22.0 ], 173 | "text" : "text readme.txt" 174 | } 175 | 176 | } 177 | , { 178 | "box" : { 179 | "fontname" : "Helvetica Neue Light", 180 | "fontsize" : 13.0, 181 | "id" : "obj-7", 182 | "linecount" : 2, 183 | "maxclass" : "message", 184 | "numinlets" : 2, 185 | "numoutlets" : 1, 186 | "outlettype" : [ "" ], 187 | "patching_rect" : [ 245.0, 570.0, 97.0, 35.0 ], 188 | "text" : ";\rtest.master run" 189 | } 190 | 191 | } 192 | , { 193 | "box" : { 194 | "fontname" : "Helvetica Neue Light", 195 | "fontsize" : 13.0, 196 | "id" : "obj-6", 197 | "linecount" : 2, 198 | "maxclass" : "message", 199 | "numinlets" : 2, 200 | "numoutlets" : 1, 201 | "outlettype" : [ "" ], 202 | "patching_rect" : [ 245.0, 240.0, 157.0, 35.0 ], 203 | "text" : ";\rtest.master run round :list" 204 | } 205 | 206 | } 207 | , { 208 | "box" : { 209 | "fontname" : "Helvetica Neue Light", 210 | "fontsize" : 13.0, 211 | "id" : "obj-5", 212 | "linecount" : 2, 213 | "maxclass" : "message", 214 | "numinlets" : 2, 215 | "numoutlets" : 1, 216 | "outlettype" : [ "" ], 217 | "patching_rect" : [ 245.0, 385.0, 153.0, 35.0 ], 218 | "text" : ";\rtest.master run :anything" 219 | } 220 | 221 | } 222 | , { 223 | "box" : { 224 | "fontname" : "Helvetica Neue Light", 225 | "fontsize" : 13.0, 226 | "id" : "obj-4", 227 | "linecount" : 2, 228 | "maxclass" : "message", 229 | "numinlets" : 2, 230 | "numoutlets" : 1, 231 | "outlettype" : [ "" ], 232 | "patching_rect" : [ 245.0, 345.0, 120.0, 35.0 ], 233 | "text" : ";\rtest.master run :list" 234 | } 235 | 236 | } 237 | , { 238 | "box" : { 239 | "fontname" : "Helvetica Neue Light", 240 | "fontsize" : 13.0, 241 | "id" : "obj-3", 242 | "linecount" : 2, 243 | "maxclass" : "message", 244 | "numinlets" : 2, 245 | "numoutlets" : 1, 246 | "outlettype" : [ "" ], 247 | "patching_rect" : [ 245.0, 150.0, 134.0, 35.0 ], 248 | "text" : ";\rtest.master run round" 249 | } 250 | 251 | } 252 | , { 253 | "box" : { 254 | "fontname" : "Helvetica Neue Light", 255 | "fontsize" : 13.0, 256 | "id" : "obj-2", 257 | "linecount" : 2, 258 | "maxclass" : "message", 259 | "numinlets" : 2, 260 | "numoutlets" : 1, 261 | "outlettype" : [ "" ], 262 | "patching_rect" : [ 245.0, 105.0, 109.0, 35.0 ], 263 | "text" : ";\rtest.master run zl" 264 | } 265 | 266 | } 267 | ], 268 | "lines" : [ { 269 | "patchline" : { 270 | "destination" : [ "obj-1", 0 ], 271 | "disabled" : 0, 272 | "hidden" : 0, 273 | "source" : [ "obj-9", 0 ] 274 | } 275 | 276 | } 277 | ], 278 | "dependency_cache" : [ ] 279 | } 280 | 281 | } 282 | -------------------------------------------------------------------------------- /help/oscar.maxhelp: -------------------------------------------------------------------------------- 1 | { 2 | "patcher" : { 3 | "fileversion" : 1, 4 | "appversion" : { 5 | "major" : 7, 6 | "minor" : 0, 7 | "revision" : 0, 8 | "architecture" : "x86" 9 | } 10 | , 11 | "rect" : [ 100.0, 100.0, 879.0, 685.0 ], 12 | "bglocked" : 0, 13 | "openinpresentation" : 0, 14 | "default_fontsize" : 13.0, 15 | "default_fontface" : 0, 16 | "default_fontname" : "Helvetica Neue Light", 17 | "gridonopen" : 0, 18 | "gridsize" : [ 5.0, 5.0 ], 19 | "gridsnaponopen" : 0, 20 | "statusbarvisible" : 2, 21 | "toolbarvisible" : 1, 22 | "boxanimatetime" : 200, 23 | "imprint" : 0, 24 | "enablehscroll" : 1, 25 | "enablevscroll" : 1, 26 | "devicewidth" : 0.0, 27 | "description" : "", 28 | "digest" : "", 29 | "tags" : "", 30 | "boxes" : [ { 31 | "box" : { 32 | "fontname" : "Helvetica Neue Light", 33 | "fontsize" : 29.996721, 34 | "frgb" : 0.0, 35 | "id" : "obj-22", 36 | "maxclass" : "comment", 37 | "numinlets" : 1, 38 | "numoutlets" : 0, 39 | "patching_rect" : [ 15.0, 385.0, 210.0, 42.0 ], 40 | "text" : "Authoring Tests" 41 | } 42 | 43 | } 44 | , { 45 | "box" : { 46 | "fontname" : "Helvetica Neue Light", 47 | "fontsize" : 29.996721, 48 | "frgb" : 0.0, 49 | "id" : "obj-20", 50 | "maxclass" : "comment", 51 | "numinlets" : 1, 52 | "numoutlets" : 0, 53 | "patching_rect" : [ 10.0, 155.0, 190.0, 42.0 ], 54 | "text" : "Running Tests" 55 | } 56 | 57 | } 58 | , { 59 | "box" : { 60 | "fontname" : "Helvetica Neue Light", 61 | "fontsize" : 61.590477, 62 | "frgb" : 0.0, 63 | "id" : "obj-19", 64 | "maxclass" : "comment", 65 | "numinlets" : 1, 66 | "numoutlets" : 0, 67 | "patching_rect" : [ 5.0, 5.0, 540.0, 79.0 ], 68 | "text" : "oscar" 69 | } 70 | 71 | } 72 | , { 73 | "box" : { 74 | "fontname" : "Helvetica Neue Light", 75 | "fontsize" : 13.0, 76 | "frgb" : 0.0, 77 | "id" : "obj-14", 78 | "linecount" : 2, 79 | "maxclass" : "comment", 80 | "numinlets" : 1, 81 | "numoutlets" : 0, 82 | "patching_rect" : [ 5.0, 75.0, 540.0, 37.0 ], 83 | "text" : "The 'oscar' extension provides the infrastructure for creating and executing automatable test patchers. More information is available in the \"ReadMe\" file accompanyign this help patcher." 84 | } 85 | 86 | } 87 | , { 88 | "box" : { 89 | "color" : [ 1.0, 0.66, 0.0, 1.0 ], 90 | "fontname" : "Arial", 91 | "fontsize" : 12.0, 92 | "id" : "obj-32", 93 | "maxclass" : "newobj", 94 | "numinlets" : 2, 95 | "numoutlets" : 1, 96 | "outlettype" : [ "" ], 97 | "patching_rect" : [ 18.0, 474.078552, 69.0, 20.0 ], 98 | "text" : "test.equals" 99 | } 100 | 101 | } 102 | , { 103 | "box" : { 104 | "fontname" : "Helvetica Neue Light", 105 | "fontsize" : 13.0, 106 | "frgb" : 0.0, 107 | "id" : "obj-31", 108 | "maxclass" : "comment", 109 | "numinlets" : 1, 110 | "numoutlets" : 0, 111 | "patching_rect" : [ 125.0, 445.0, 598.0, 22.0 ], 112 | "text" : "test.assert takes a 1 (success) or 0 (fail) as input to evaluate the success of an operation in a test patcher." 113 | } 114 | 115 | } 116 | , { 117 | "box" : { 118 | "fontname" : "Helvetica Neue Light", 119 | "fontsize" : 13.0, 120 | "frgb" : 0.0, 121 | "id" : "obj-30", 122 | "maxclass" : "comment", 123 | "numinlets" : 1, 124 | "numoutlets" : 0, 125 | "patching_rect" : [ 125.0, 474.078552, 401.0, 22.0 ], 126 | "text" : "test.equals is somewhat like == but better suited for comparing floats." 127 | } 128 | 129 | } 130 | , { 131 | "box" : { 132 | "fontname" : "Helvetica Neue Light", 133 | "fontsize" : 13.0, 134 | "frgb" : 0.0, 135 | "id" : "obj-29", 136 | "maxclass" : "comment", 137 | "numinlets" : 1, 138 | "numoutlets" : 0, 139 | "patching_rect" : [ 125.0, 532.235718, 712.0, 22.0 ], 140 | "text" : "test.log is like the print object, except that the input will be printed to the test result database rather than just the Max window." 141 | } 142 | 143 | } 144 | , { 145 | "box" : { 146 | "fontname" : "Helvetica Neue Light", 147 | "fontsize" : 13.0, 148 | "frgb" : 0.0, 149 | "id" : "obj-25", 150 | "maxclass" : "comment", 151 | "numinlets" : 1, 152 | "numoutlets" : 0, 153 | "patching_rect" : [ 125.0, 503.157135, 292.0, 22.0 ], 154 | "text" : "test.error is an abstraction around the error object." 155 | } 156 | 157 | } 158 | , { 159 | "box" : { 160 | "fontname" : "Helvetica Neue Light", 161 | "fontsize" : 13.0, 162 | "frgb" : 0.0, 163 | "id" : "obj-21", 164 | "maxclass" : "comment", 165 | "numinlets" : 1, 166 | "numoutlets" : 0, 167 | "patching_rect" : [ 125.0, 561.31427, 490.0, 22.0 ], 168 | "text" : "test.sample~ is somewhat like snapshot~ but better suited to the testing environment." 169 | } 170 | 171 | } 172 | , { 173 | "box" : { 174 | "fontname" : "Helvetica Neue Light", 175 | "fontsize" : 13.0, 176 | "frgb" : 0.0, 177 | "id" : "obj-17", 178 | "maxclass" : "comment", 179 | "numinlets" : 1, 180 | "numoutlets" : 0, 181 | "patching_rect" : [ 125.0, 590.392822, 733.0, 22.0 ], 182 | "text" : "test.string.equals compares two strings for equality, but ignores differences between Mac line-endings and Windows line-endings." 183 | } 184 | 185 | } 186 | , { 187 | "box" : { 188 | "fontname" : "Helvetica Neue Light", 189 | "fontsize" : 13.0, 190 | "frgb" : 0.0, 191 | "id" : "obj-13", 192 | "maxclass" : "comment", 193 | "numinlets" : 1, 194 | "numoutlets" : 0, 195 | "patching_rect" : [ 125.0, 619.471375, 503.0, 22.0 ], 196 | "text" : "test.terminate takes a bang to indicate that a test is complete and should be terminated." 197 | } 198 | 199 | } 200 | , { 201 | "box" : { 202 | "color" : [ 1.0, 0.66, 0.0, 1.0 ], 203 | "fontname" : "Arial", 204 | "fontsize" : 12.0, 205 | "id" : "obj-9", 206 | "maxclass" : "newobj", 207 | "numinlets" : 1, 208 | "numoutlets" : 0, 209 | "patching_rect" : [ 18.0, 619.471375, 83.0, 20.0 ], 210 | "text" : "test.terminate" 211 | } 212 | 213 | } 214 | , { 215 | "box" : { 216 | "bgmode" : 0, 217 | "border" : 0, 218 | "clickthrough" : 0, 219 | "color" : [ 1.0, 0.66, 0.0, 1.0 ], 220 | "enablehscroll" : 0, 221 | "enablevscroll" : 0, 222 | "fontname" : "Arial", 223 | "fontsize" : 12.0, 224 | "id" : "obj-1", 225 | "lockeddragscroll" : 0, 226 | "maxclass" : "newobj", 227 | "numinlets" : 2, 228 | "numoutlets" : 1, 229 | "offset" : [ 0.0, 0.0 ], 230 | "outlettype" : [ "" ], 231 | "patching_rect" : [ 18.0, 590.392822, 101.0, 20.0 ], 232 | "text" : "test.string.equals", 233 | "viewvisibility" : 0 234 | } 235 | 236 | } 237 | , { 238 | "box" : { 239 | "color" : [ 1.0, 0.66, 0.0, 1.0 ], 240 | "fontname" : "Arial", 241 | "fontsize" : 12.0, 242 | "id" : "obj-6", 243 | "maxclass" : "newobj", 244 | "numinlets" : 1, 245 | "numoutlets" : 1, 246 | "outlettype" : [ "" ], 247 | "patching_rect" : [ 18.0, 561.31427, 79.0, 20.0 ], 248 | "text" : "test.sample~" 249 | } 250 | 251 | } 252 | , { 253 | "box" : { 254 | "color" : [ 1.0, 0.66, 0.0, 1.0 ], 255 | "fontname" : "Arial", 256 | "fontsize" : 12.0, 257 | "id" : "obj-5", 258 | "maxclass" : "newobj", 259 | "numinlets" : 1, 260 | "numoutlets" : 0, 261 | "patching_rect" : [ 18.0, 532.235718, 49.0, 20.0 ], 262 | "text" : "test.log" 263 | } 264 | 265 | } 266 | , { 267 | "box" : { 268 | "bgmode" : 0, 269 | "border" : 0, 270 | "clickthrough" : 0, 271 | "enablehscroll" : 0, 272 | "enablevscroll" : 0, 273 | "fontname" : "Arial", 274 | "fontsize" : 12.0, 275 | "id" : "obj-4", 276 | "lockeddragscroll" : 0, 277 | "maxclass" : "newobj", 278 | "numinlets" : 1, 279 | "numoutlets" : 2, 280 | "offset" : [ 0.0, 0.0 ], 281 | "outlettype" : [ "bang", "int" ], 282 | "patching_rect" : [ 18.0, 503.157135, 59.0, 20.0 ], 283 | "text" : "test.error", 284 | "viewvisibility" : 0 285 | } 286 | 287 | } 288 | , { 289 | "box" : { 290 | "color" : [ 1.0, 0.66, 0.0, 1.0 ], 291 | "fontname" : "Arial", 292 | "fontsize" : 12.0, 293 | "id" : "obj-3", 294 | "maxclass" : "newobj", 295 | "numinlets" : 1, 296 | "numoutlets" : 1, 297 | "outlettype" : [ "" ], 298 | "patching_rect" : [ 18.0, 445.0, 66.0, 20.0 ], 299 | "text" : "test.assert" 300 | } 301 | 302 | } 303 | , { 304 | "box" : { 305 | "fontname" : "Helvetica Neue Light", 306 | "fontsize" : 13.0, 307 | "frgb" : 0.0, 308 | "id" : "obj-11", 309 | "maxclass" : "comment", 310 | "numinlets" : 1, 311 | "numoutlets" : 0, 312 | "patching_rect" : [ 305.0, 210.0, 200.0, 22.0 ], 313 | "text" : "Run a named integration test" 314 | } 315 | 316 | } 317 | , { 318 | "box" : { 319 | "fontname" : "Helvetica Neue Light", 320 | "fontsize" : 13.0, 321 | "id" : "obj-8", 322 | "linecount" : 2, 323 | "maxclass" : "message", 324 | "numinlets" : 2, 325 | "numoutlets" : 1, 326 | "outlettype" : [ "" ], 327 | "patching_rect" : [ 15.0, 205.0, 279.0, 35.0 ], 328 | "text" : ";\rtest.master run 4505-dict-notification-js.maxtest" 329 | } 330 | 331 | } 332 | , { 333 | "box" : { 334 | "fontname" : "Helvetica Neue Light", 335 | "fontsize" : 13.0, 336 | "frgb" : 0.0, 337 | "id" : "obj-12", 338 | "linecount" : 3, 339 | "maxclass" : "comment", 340 | "numinlets" : 1, 341 | "numoutlets" : 0, 342 | "patching_rect" : [ 305.0, 255.0, 215.0, 53.0 ], 343 | "text" : "Run all tests (uses the database, so you might need to wait a minute or two after launch)" 344 | } 345 | 346 | } 347 | , { 348 | "box" : { 349 | "fontname" : "Helvetica Neue Light", 350 | "fontsize" : 13.0, 351 | "frgb" : 0.0, 352 | "id" : "obj-10", 353 | "maxclass" : "comment", 354 | "numinlets" : 1, 355 | "numoutlets" : 0, 356 | "patching_rect" : [ 85.0, 325.0, 375.0, 22.0 ], 357 | "text" : "test results are in a SQLite database in the 'testpackage' folder" 358 | } 359 | 360 | } 361 | , { 362 | "box" : { 363 | "fontname" : "Helvetica Neue Light", 364 | "fontsize" : 13.0, 365 | "id" : "obj-7", 366 | "linecount" : 2, 367 | "maxclass" : "message", 368 | "numinlets" : 2, 369 | "numoutlets" : 1, 370 | "outlettype" : [ "" ], 371 | "patching_rect" : [ 195.0, 260.0, 97.0, 35.0 ], 372 | "text" : ";\rtest.master run" 373 | } 374 | 375 | } 376 | ], 377 | "lines" : [ ], 378 | "dependency_cache" : [ { 379 | "name" : "test.error.maxpat", 380 | "bootpath" : "/Users/tim/Code/Max/testing/testpackage/misc", 381 | "patcherrelativepath" : "../misc", 382 | "type" : "JSON", 383 | "implicit" : 1 384 | } 385 | , { 386 | "name" : "test.string.equals.maxpat", 387 | "bootpath" : "/Users/tim/Code/Max/testing/testpackage/misc", 388 | "patcherrelativepath" : "../misc", 389 | "type" : "JSON", 390 | "implicit" : 1 391 | } 392 | , { 393 | "name" : "test.string.equals.js", 394 | "bootpath" : "/Users/tim/Code/Max/testing/testpackage/misc", 395 | "patcherrelativepath" : "../misc", 396 | "type" : "TEXT", 397 | "implicit" : 1 398 | } 399 | ] 400 | } 401 | 402 | } 403 | -------------------------------------------------------------------------------- /help/test.log.maxhelp: -------------------------------------------------------------------------------- 1 | { 2 | "patcher" : { 3 | "fileversion" : 1, 4 | "appversion" : { 5 | "major" : 7, 6 | "minor" : 0, 7 | "revision" : 0, 8 | "architecture" : "x64" 9 | } 10 | , 11 | "rect" : [ 100.0, 100.0, 597.0, 393.0 ], 12 | "bglocked" : 0, 13 | "openinpresentation" : 0, 14 | "default_fontsize" : 12.0, 15 | "default_fontface" : 0, 16 | "default_fontname" : "Arial", 17 | "gridonopen" : 0, 18 | "gridsize" : [ 15.0, 15.0 ], 19 | "gridsnaponopen" : 0, 20 | "statusbarvisible" : 2, 21 | "toolbarvisible" : 1, 22 | "boxanimatetime" : 200, 23 | "imprint" : 0, 24 | "enablehscroll" : 1, 25 | "enablevscroll" : 1, 26 | "devicewidth" : 0.0, 27 | "description" : "", 28 | "digest" : "", 29 | "tags" : "", 30 | "showrootpatcherontab" : 0, 31 | "showontab" : 0, 32 | "boxes" : [ { 33 | "box" : { 34 | "bgmode" : 0, 35 | "border" : 0, 36 | "clickthrough" : 0, 37 | "enablehscroll" : 0, 38 | "enablevscroll" : 0, 39 | "fontname" : "Arial", 40 | "fontsize" : 12.0, 41 | "id" : "obj-2", 42 | "lockeddragscroll" : 0, 43 | "maxclass" : "newobj", 44 | "numinlets" : 0, 45 | "numoutlets" : 0, 46 | "offset" : [ 0.0, 0.0 ], 47 | "patcher" : { 48 | "fileversion" : 1, 49 | "appversion" : { 50 | "major" : 7, 51 | "minor" : 0, 52 | "revision" : 0, 53 | "architecture" : "x64" 54 | } 55 | , 56 | "rect" : [ 100.0, 126.0, 597.0, 367.0 ], 57 | "bglocked" : 0, 58 | "openinpresentation" : 0, 59 | "default_fontsize" : 13.0, 60 | "default_fontface" : 0, 61 | "default_fontname" : "Arial", 62 | "gridonopen" : 0, 63 | "gridsize" : [ 5.0, 5.0 ], 64 | "gridsnaponopen" : 0, 65 | "statusbarvisible" : 2, 66 | "toolbarvisible" : 1, 67 | "boxanimatetime" : 200, 68 | "imprint" : 0, 69 | "enablehscroll" : 1, 70 | "enablevscroll" : 1, 71 | "devicewidth" : 0.0, 72 | "description" : "", 73 | "digest" : "", 74 | "tags" : "", 75 | "showontab" : 1, 76 | "boxes" : [ { 77 | "box" : { 78 | "fontname" : "Arial", 79 | "fontsize" : 13.0, 80 | "id" : "obj-4", 81 | "maxclass" : "message", 82 | "numinlets" : 2, 83 | "numoutlets" : 1, 84 | "outlettype" : [ "" ], 85 | "patching_rect" : [ 155.0, 215.0, 73.0, 19.0 ], 86 | "text" : "hello world" 87 | } 88 | 89 | } 90 | , { 91 | "box" : { 92 | "color" : [ 1.0, 0.66, 0.0, 1.0 ], 93 | "fontname" : "Arial", 94 | "fontsize" : 13.0, 95 | "id" : "obj-1", 96 | "maxclass" : "newobj", 97 | "numinlets" : 1, 98 | "numoutlets" : 0, 99 | "patching_rect" : [ 155.0, 260.0, 52.0, 21.0 ], 100 | "text" : "test.log" 101 | } 102 | 103 | } 104 | , { 105 | "box" : { 106 | "border" : 0, 107 | "filename" : "helpdetails.js", 108 | "id" : "obj-2", 109 | "ignoreclick" : 1, 110 | "jsarguments" : [ "test.log" ], 111 | "maxclass" : "jsui", 112 | "numinlets" : 1, 113 | "numoutlets" : 1, 114 | "outlettype" : [ "" ], 115 | "parameter_enable" : 0, 116 | "patching_rect" : [ 10.0, 10.0, 515.0, 115.0 ] 117 | } 118 | 119 | } 120 | ], 121 | "lines" : [ { 122 | "patchline" : { 123 | "destination" : [ "obj-1", 0 ], 124 | "disabled" : 0, 125 | "hidden" : 0, 126 | "source" : [ "obj-4", 0 ] 127 | } 128 | 129 | } 130 | ] 131 | } 132 | , 133 | "patching_rect" : [ 10.0, 85.0, 50.0, 20.0 ], 134 | "saved_object_attributes" : { 135 | "default_fontface" : 0, 136 | "default_fontname" : "Arial", 137 | "default_fontsize" : 13.0, 138 | "description" : "", 139 | "digest" : "", 140 | "fontface" : 0, 141 | "fontname" : "Arial", 142 | "fontsize" : 13.0, 143 | "globalpatchername" : "", 144 | "tags" : "" 145 | } 146 | , 147 | "text" : "p basic", 148 | "varname" : "basic_tab", 149 | "viewvisibility" : 0 150 | } 151 | 152 | } 153 | , { 154 | "box" : { 155 | "bgmode" : 0, 156 | "border" : 0, 157 | "clickthrough" : 0, 158 | "enablehscroll" : 0, 159 | "enablevscroll" : 0, 160 | "fontname" : "Arial", 161 | "fontsize" : 12.0, 162 | "id" : "obj-3", 163 | "lockeddragscroll" : 0, 164 | "maxclass" : "newobj", 165 | "numinlets" : 0, 166 | "numoutlets" : 0, 167 | "offset" : [ 0.0, 0.0 ], 168 | "patcher" : { 169 | "fileversion" : 1, 170 | "appversion" : { 171 | "major" : 7, 172 | "minor" : 0, 173 | "revision" : 0, 174 | "architecture" : "x64" 175 | } 176 | , 177 | "rect" : [ 0.0, 26.0, 597.0, 367.0 ], 178 | "bglocked" : 0, 179 | "openinpresentation" : 0, 180 | "default_fontsize" : 13.0, 181 | "default_fontface" : 0, 182 | "default_fontname" : "Arial", 183 | "gridonopen" : 0, 184 | "gridsize" : [ 5.0, 5.0 ], 185 | "gridsnaponopen" : 0, 186 | "statusbarvisible" : 2, 187 | "toolbarvisible" : 1, 188 | "boxanimatetime" : 200, 189 | "imprint" : 0, 190 | "enablehscroll" : 1, 191 | "enablevscroll" : 1, 192 | "devicewidth" : 0.0, 193 | "description" : "", 194 | "digest" : "", 195 | "tags" : "", 196 | "showontab" : 1, 197 | "boxes" : [ ], 198 | "lines" : [ ] 199 | } 200 | , 201 | "patching_rect" : [ 23.0, 116.0, 50.0, 20.0 ], 202 | "saved_object_attributes" : { 203 | "default_fontface" : 0, 204 | "default_fontname" : "Arial", 205 | "default_fontsize" : 13.0, 206 | "description" : "", 207 | "digest" : "", 208 | "fontface" : 0, 209 | "fontname" : "Arial", 210 | "fontsize" : 13.0, 211 | "globalpatchername" : "", 212 | "tags" : "" 213 | } 214 | , 215 | "text" : "p ?", 216 | "varname" : "q_tab", 217 | "viewvisibility" : 0 218 | } 219 | 220 | } 221 | ], 222 | "lines" : [ ], 223 | "dependency_cache" : [ { 224 | "name" : "helpdetails.js", 225 | "bootpath" : "/Users/tim/Code/Max/maxmsp-misc/help/resources", 226 | "patcherrelativepath" : "../../../maxmsp-misc/help/resources", 227 | "type" : "TEXT", 228 | "implicit" : 1 229 | } 230 | ] 231 | } 232 | 233 | } 234 | -------------------------------------------------------------------------------- /help/test.string.equals.maxhelp: -------------------------------------------------------------------------------- 1 | { 2 | "patcher" : { 3 | "fileversion" : 1, 4 | "appversion" : { 5 | "major" : 7, 6 | "minor" : 0, 7 | "revision" : 0, 8 | "architecture" : "x64" 9 | } 10 | , 11 | "rect" : [ 100.0, 100.0, 597.0, 393.0 ], 12 | "bglocked" : 0, 13 | "openinpresentation" : 0, 14 | "default_fontsize" : 12.0, 15 | "default_fontface" : 0, 16 | "default_fontname" : "Arial", 17 | "gridonopen" : 0, 18 | "gridsize" : [ 15.0, 15.0 ], 19 | "gridsnaponopen" : 0, 20 | "statusbarvisible" : 2, 21 | "toolbarvisible" : 1, 22 | "boxanimatetime" : 200, 23 | "imprint" : 0, 24 | "enablehscroll" : 1, 25 | "enablevscroll" : 1, 26 | "devicewidth" : 0.0, 27 | "description" : "", 28 | "digest" : "", 29 | "tags" : "", 30 | "showrootpatcherontab" : 0, 31 | "showontab" : 0, 32 | "boxes" : [ { 33 | "box" : { 34 | "bgmode" : 0, 35 | "border" : 0, 36 | "clickthrough" : 0, 37 | "enablehscroll" : 0, 38 | "enablevscroll" : 0, 39 | "fontname" : "Arial", 40 | "fontsize" : 12.0, 41 | "id" : "obj-2", 42 | "lockeddragscroll" : 0, 43 | "maxclass" : "newobj", 44 | "numinlets" : 0, 45 | "numoutlets" : 0, 46 | "offset" : [ 0.0, 0.0 ], 47 | "patcher" : { 48 | "fileversion" : 1, 49 | "appversion" : { 50 | "major" : 7, 51 | "minor" : 0, 52 | "revision" : 0, 53 | "architecture" : "x64" 54 | } 55 | , 56 | "rect" : [ 100.0, 126.0, 597.0, 367.0 ], 57 | "bglocked" : 0, 58 | "openinpresentation" : 0, 59 | "default_fontsize" : 13.0, 60 | "default_fontface" : 0, 61 | "default_fontname" : "Arial", 62 | "gridonopen" : 0, 63 | "gridsize" : [ 5.0, 5.0 ], 64 | "gridsnaponopen" : 0, 65 | "statusbarvisible" : 2, 66 | "toolbarvisible" : 1, 67 | "boxanimatetime" : 200, 68 | "imprint" : 0, 69 | "enablehscroll" : 1, 70 | "enablevscroll" : 1, 71 | "devicewidth" : 0.0, 72 | "description" : "", 73 | "digest" : "", 74 | "tags" : "", 75 | "showontab" : 1, 76 | "boxes" : [ { 77 | "box" : { 78 | "fontname" : "Arial", 79 | "fontsize" : 13.0, 80 | "id" : "obj-14", 81 | "maxclass" : "message", 82 | "numinlets" : 2, 83 | "numoutlets" : 1, 84 | "outlettype" : [ "" ], 85 | "patching_rect" : [ 120.0, 220.0, 32.5, 19.0 ], 86 | "text" : "1" 87 | } 88 | 89 | } 90 | , { 91 | "box" : { 92 | "id" : "obj-11", 93 | "maxclass" : "button", 94 | "numinlets" : 1, 95 | "numoutlets" : 1, 96 | "outlettype" : [ "bang" ], 97 | "patching_rect" : [ 120.0, 195.0, 20.0, 20.0 ] 98 | } 99 | 100 | } 101 | , { 102 | "box" : { 103 | "coll_data" : { 104 | "count" : 1, 105 | "data" : [ { 106 | "key" : 1, 107 | "value" : [ "Many", "men", "go", "fishing", "all", "of", "their", "lives", "without", "knowing", "that", "it", "is", "not", "fish", "they", "are", "after." ] 108 | } 109 | ] 110 | } 111 | , 112 | "fontname" : "Arial", 113 | "fontsize" : 13.0, 114 | "id" : "obj-9", 115 | "maxclass" : "newobj", 116 | "numinlets" : 1, 117 | "numoutlets" : 4, 118 | "outlettype" : [ "", "", "", "" ], 119 | "patching_rect" : [ 120.0, 245.0, 161.0, 21.0 ], 120 | "saved_object_attributes" : { 121 | "embed" : 1 122 | } 123 | , 124 | "text" : "coll henry-david-thoreau 1" 125 | } 126 | 127 | } 128 | , { 129 | "box" : { 130 | "fontname" : "Arial", 131 | "fontsize" : 13.0, 132 | "id" : "obj-7", 133 | "linecount" : 3, 134 | "maxclass" : "message", 135 | "numinlets" : 2, 136 | "numoutlets" : 1, 137 | "outlettype" : [ "" ], 138 | "patching_rect" : [ 295.0, 195.0, 195.0, 48.0 ], 139 | "text" : "Many men go fishing all of their lives without knowing that it is not fish they are after." 140 | } 141 | 142 | } 143 | , { 144 | "box" : { 145 | "fontname" : "Arial", 146 | "fontsize" : 13.0, 147 | "id" : "obj-5", 148 | "maxclass" : "newobj", 149 | "numinlets" : 1, 150 | "numoutlets" : 1, 151 | "outlettype" : [ "bang" ], 152 | "patching_rect" : [ 200.0, 145.0, 64.0, 21.0 ], 153 | "text" : "loadbang" 154 | } 155 | 156 | } 157 | , { 158 | "box" : { 159 | "id" : "obj-4", 160 | "maxclass" : "toggle", 161 | "numinlets" : 1, 162 | "numoutlets" : 1, 163 | "outlettype" : [ "int" ], 164 | "parameter_enable" : 0, 165 | "patching_rect" : [ 205.0, 320.0, 35.0, 35.0 ] 166 | } 167 | 168 | } 169 | , { 170 | "box" : { 171 | "bgmode" : 0, 172 | "border" : 0, 173 | "clickthrough" : 0, 174 | "enablehscroll" : 0, 175 | "enablevscroll" : 0, 176 | "fontname" : "Arial", 177 | "fontsize" : 13.0, 178 | "id" : "obj-1", 179 | "lockeddragscroll" : 0, 180 | "maxclass" : "newobj", 181 | "numinlets" : 2, 182 | "numoutlets" : 1, 183 | "offset" : [ 0.0, 0.0 ], 184 | "outlettype" : [ "" ], 185 | "patching_rect" : [ 205.0, 285.0, 109.0, 21.0 ], 186 | "text" : "test.string.equals", 187 | "viewvisibility" : 0 188 | } 189 | 190 | } 191 | , { 192 | "box" : { 193 | "border" : 0, 194 | "filename" : "helpdetails.js", 195 | "id" : "obj-2", 196 | "ignoreclick" : 1, 197 | "jsarguments" : [ "test.string.equals" ], 198 | "maxclass" : "jsui", 199 | "numinlets" : 1, 200 | "numoutlets" : 1, 201 | "outlettype" : [ "" ], 202 | "parameter_enable" : 0, 203 | "patching_rect" : [ 10.0, 10.0, 540.0, 115.0 ] 204 | } 205 | 206 | } 207 | ], 208 | "lines" : [ { 209 | "patchline" : { 210 | "destination" : [ "obj-4", 0 ], 211 | "disabled" : 0, 212 | "hidden" : 0, 213 | "source" : [ "obj-1", 0 ] 214 | } 215 | 216 | } 217 | , { 218 | "patchline" : { 219 | "destination" : [ "obj-14", 0 ], 220 | "disabled" : 0, 221 | "hidden" : 0, 222 | "source" : [ "obj-11", 0 ] 223 | } 224 | 225 | } 226 | , { 227 | "patchline" : { 228 | "destination" : [ "obj-9", 0 ], 229 | "disabled" : 0, 230 | "hidden" : 0, 231 | "source" : [ "obj-14", 0 ] 232 | } 233 | 234 | } 235 | , { 236 | "patchline" : { 237 | "destination" : [ "obj-11", 0 ], 238 | "disabled" : 0, 239 | "hidden" : 0, 240 | "source" : [ "obj-5", 0 ] 241 | } 242 | 243 | } 244 | , { 245 | "patchline" : { 246 | "destination" : [ "obj-7", 0 ], 247 | "disabled" : 0, 248 | "hidden" : 0, 249 | "source" : [ "obj-5", 0 ] 250 | } 251 | 252 | } 253 | , { 254 | "patchline" : { 255 | "destination" : [ "obj-1", 1 ], 256 | "disabled" : 0, 257 | "hidden" : 0, 258 | "source" : [ "obj-7", 0 ] 259 | } 260 | 261 | } 262 | , { 263 | "patchline" : { 264 | "destination" : [ "obj-1", 0 ], 265 | "disabled" : 0, 266 | "hidden" : 0, 267 | "source" : [ "obj-9", 0 ] 268 | } 269 | 270 | } 271 | ] 272 | } 273 | , 274 | "patching_rect" : [ 10.0, 85.0, 50.0, 20.0 ], 275 | "saved_object_attributes" : { 276 | "default_fontface" : 0, 277 | "default_fontname" : "Arial", 278 | "default_fontsize" : 13.0, 279 | "description" : "", 280 | "digest" : "", 281 | "fontface" : 0, 282 | "fontname" : "Arial", 283 | "fontsize" : 13.0, 284 | "globalpatchername" : "", 285 | "tags" : "" 286 | } 287 | , 288 | "text" : "p basic", 289 | "varname" : "basic_tab", 290 | "viewvisibility" : 0 291 | } 292 | 293 | } 294 | , { 295 | "box" : { 296 | "bgmode" : 0, 297 | "border" : 0, 298 | "clickthrough" : 0, 299 | "enablehscroll" : 0, 300 | "enablevscroll" : 0, 301 | "fontname" : "Arial", 302 | "fontsize" : 12.0, 303 | "id" : "obj-3", 304 | "lockeddragscroll" : 0, 305 | "maxclass" : "newobj", 306 | "numinlets" : 0, 307 | "numoutlets" : 0, 308 | "offset" : [ 0.0, 0.0 ], 309 | "patcher" : { 310 | "fileversion" : 1, 311 | "appversion" : { 312 | "major" : 7, 313 | "minor" : 0, 314 | "revision" : 0, 315 | "architecture" : "x64" 316 | } 317 | , 318 | "rect" : [ 0.0, 26.0, 597.0, 367.0 ], 319 | "bglocked" : 0, 320 | "openinpresentation" : 0, 321 | "default_fontsize" : 13.0, 322 | "default_fontface" : 0, 323 | "default_fontname" : "Arial", 324 | "gridonopen" : 0, 325 | "gridsize" : [ 5.0, 5.0 ], 326 | "gridsnaponopen" : 0, 327 | "statusbarvisible" : 2, 328 | "toolbarvisible" : 1, 329 | "boxanimatetime" : 200, 330 | "imprint" : 0, 331 | "enablehscroll" : 1, 332 | "enablevscroll" : 1, 333 | "devicewidth" : 0.0, 334 | "description" : "", 335 | "digest" : "", 336 | "tags" : "", 337 | "showontab" : 1, 338 | "boxes" : [ ], 339 | "lines" : [ ] 340 | } 341 | , 342 | "patching_rect" : [ 23.0, 116.0, 50.0, 20.0 ], 343 | "saved_object_attributes" : { 344 | "default_fontface" : 0, 345 | "default_fontname" : "Arial", 346 | "default_fontsize" : 13.0, 347 | "description" : "", 348 | "digest" : "", 349 | "fontface" : 0, 350 | "fontname" : "Arial", 351 | "fontsize" : 13.0, 352 | "globalpatchername" : "", 353 | "tags" : "" 354 | } 355 | , 356 | "text" : "p ?", 357 | "varname" : "q_tab", 358 | "viewvisibility" : 0 359 | } 360 | 361 | } 362 | ], 363 | "lines" : [ ], 364 | "dependency_cache" : [ { 365 | "name" : "helpdetails.js", 366 | "bootpath" : "/Users/tim/Code/Max/maxmsp-misc/help/resources", 367 | "patcherrelativepath" : "../../../maxmsp-misc/help/resources", 368 | "type" : "TEXT", 369 | "implicit" : 1 370 | } 371 | , { 372 | "name" : "test.string.equals.maxpat", 373 | "bootpath" : "/Users/tim/Code/Max/testing/testpackage/misc", 374 | "patcherrelativepath" : "../misc", 375 | "type" : "JSON", 376 | "implicit" : 1 377 | } 378 | , { 379 | "name" : "test.string.equals.js", 380 | "bootpath" : "/Users/tim/Code/Max/testing/testpackage/misc", 381 | "patcherrelativepath" : "../misc", 382 | "type" : "TEXT", 383 | "implicit" : 1 384 | } 385 | ] 386 | } 387 | 388 | } 389 | -------------------------------------------------------------------------------- /init/testpackage-init.txt: -------------------------------------------------------------------------------- 1 | max objectfile test.assert oscar test.assert; 2 | max objectfile test.equals oscar test.equals; 3 | max objectfile test.log oscar test.log; 4 | max objectfile test.sample~ oscar test.sample~; 5 | max objectfile test.terminate oscar test.terminate; 6 | -------------------------------------------------------------------------------- /interfaces/testpackage.db.json: -------------------------------------------------------------------------------- 1 | { 2 | "maxdb" : { 3 | "internals" : [ "test.assert", 4 | "test.equals", 5 | "test.log", 6 | "test.terminate", 7 | "test.sample~" 8 | ] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /misc/max-test-config-example.json: -------------------------------------------------------------------------------- 1 | { 2 | "port-send" : 4792, 3 | "port-listen" : 4791 4 | } 5 | -------------------------------------------------------------------------------- /misc/test.error.maxpat: -------------------------------------------------------------------------------- 1 | { 2 | "patcher" : { 3 | "fileversion" : 1, 4 | "appversion" : { 5 | "major" : 6, 6 | "minor" : 1, 7 | "revision" : 1, 8 | "architecture" : "x86" 9 | } 10 | , 11 | "rect" : [ 310.0, 210.0, 647.0, 623.0 ], 12 | "bglocked" : 0, 13 | "openinpresentation" : 0, 14 | "default_fontsize" : 12.0, 15 | "default_fontface" : 0, 16 | "default_fontname" : "Helvetica Neue Light", 17 | "gridonopen" : 0, 18 | "gridsize" : [ 5.0, 5.0 ], 19 | "gridsnaponopen" : 0, 20 | "statusbarvisible" : 2, 21 | "toolbarvisible" : 1, 22 | "boxanimatetime" : 200, 23 | "imprint" : 0, 24 | "enablehscroll" : 1, 25 | "enablevscroll" : 1, 26 | "devicewidth" : 0.0, 27 | "description" : "", 28 | "digest" : "", 29 | "tags" : "", 30 | "boxes" : [ { 31 | "box" : { 32 | "fontname" : "Helvetica Neue Light", 33 | "fontsize" : 12.0, 34 | "id" : "obj-8", 35 | "maxclass" : "message", 36 | "numinlets" : 2, 37 | "numoutlets" : 1, 38 | "outlettype" : [ "" ], 39 | "patching_rect" : [ 320.0, 355.0, 32.5, 19.0 ], 40 | "text" : "0" 41 | } 42 | 43 | } 44 | , { 45 | "box" : { 46 | "fontname" : "Helvetica Neue Light", 47 | "fontsize" : 12.0, 48 | "id" : "obj-28", 49 | "maxclass" : "message", 50 | "numinlets" : 2, 51 | "numoutlets" : 1, 52 | "outlettype" : [ "" ], 53 | "patching_rect" : [ 275.0, 355.0, 32.5, 19.0 ], 54 | "text" : "1" 55 | } 56 | 57 | } 58 | , { 59 | "box" : { 60 | "fontname" : "Helvetica Neue Light", 61 | "fontsize" : 12.0, 62 | "id" : "obj-26", 63 | "maxclass" : "newobj", 64 | "numinlets" : 1, 65 | "numoutlets" : 1, 66 | "outlettype" : [ "int" ], 67 | "patching_rect" : [ 275.0, 440.0, 24.0, 21.0 ], 68 | "text" : "t 1" 69 | } 70 | 71 | } 72 | , { 73 | "box" : { 74 | "id" : "obj-23", 75 | "maxclass" : "toggle", 76 | "numinlets" : 1, 77 | "numoutlets" : 1, 78 | "outlettype" : [ "int" ], 79 | "parameter_enable" : 0, 80 | "patching_rect" : [ 275.0, 390.0, 20.0, 20.0 ] 81 | } 82 | 83 | } 84 | , { 85 | "box" : { 86 | "fontname" : "Helvetica Neue Light", 87 | "fontsize" : 12.0, 88 | "id" : "obj-21", 89 | "maxclass" : "newobj", 90 | "numinlets" : 1, 91 | "numoutlets" : 1, 92 | "outlettype" : [ "" ], 93 | "patching_rect" : [ 275.0, 415.0, 35.0, 21.0 ], 94 | "text" : "error" 95 | } 96 | 97 | } 98 | , { 99 | "box" : { 100 | "fontname" : "Helvetica Neue Light", 101 | "fontsize" : 12.0, 102 | "frgb" : 0.0, 103 | "id" : "obj-20", 104 | "maxclass" : "comment", 105 | "numinlets" : 1, 106 | "numoutlets" : 0, 107 | "patching_rect" : [ 195.0, 175.0, 175.0, 21.0 ], 108 | "text" : "1 turns on the test, 0 turns it off" 109 | } 110 | 111 | } 112 | , { 113 | "box" : { 114 | "fontname" : "Helvetica Neue Light", 115 | "fontsize" : 12.0, 116 | "frgb" : 0.0, 117 | "id" : "obj-18", 118 | "maxclass" : "comment", 119 | "numinlets" : 1, 120 | "numoutlets" : 0, 121 | "patching_rect" : [ 60.0, 270.0, 150.0, 21.0 ], 122 | "text" : "give it time to trap the error" 123 | } 124 | 125 | } 126 | , { 127 | "box" : { 128 | "fontname" : "Helvetica Neue Light", 129 | "fontsize" : 12.0, 130 | "frgb" : 0.0, 131 | "id" : "obj-16", 132 | "maxclass" : "comment", 133 | "numinlets" : 1, 134 | "numoutlets" : 0, 135 | "patching_rect" : [ 285.0, 560.0, 90.0, 21.0 ], 136 | "text" : "result (pass/fail)" 137 | } 138 | 139 | } 140 | , { 141 | "box" : { 142 | "comment" : "", 143 | "id" : "obj-17", 144 | "maxclass" : "outlet", 145 | "numinlets" : 1, 146 | "numoutlets" : 0, 147 | "patching_rect" : [ 260.0, 560.0, 25.0, 25.0 ] 148 | } 149 | 150 | } 151 | , { 152 | "box" : { 153 | "fontname" : "Helvetica Neue Light", 154 | "fontsize" : 12.0, 155 | "frgb" : 0.0, 156 | "id" : "obj-15", 157 | "maxclass" : "comment", 158 | "numinlets" : 1, 159 | "numoutlets" : 0, 160 | "patching_rect" : [ 50.0, 565.0, 37.0, 21.0 ], 161 | "text" : "done" 162 | } 163 | 164 | } 165 | , { 166 | "box" : { 167 | "comment" : "", 168 | "id" : "obj-14", 169 | "maxclass" : "outlet", 170 | "numinlets" : 1, 171 | "numoutlets" : 0, 172 | "patching_rect" : [ 25.0, 565.0, 25.0, 25.0 ] 173 | } 174 | 175 | } 176 | , { 177 | "box" : { 178 | "fontname" : "Helvetica Neue Light", 179 | "fontsize" : 12.0, 180 | "id" : "obj-13", 181 | "maxclass" : "newobj", 182 | "numinlets" : 1, 183 | "numoutlets" : 3, 184 | "outlettype" : [ "bang", "bang", "int" ], 185 | "patching_rect" : [ 25.0, 295.0, 46.0, 21.0 ], 186 | "text" : "t b b 0" 187 | } 188 | 189 | } 190 | , { 191 | "box" : { 192 | "fontname" : "Helvetica Neue Light", 193 | "fontsize" : 12.0, 194 | "id" : "obj-10", 195 | "maxclass" : "newobj", 196 | "numinlets" : 2, 197 | "numoutlets" : 1, 198 | "outlettype" : [ "" ], 199 | "patching_rect" : [ 25.0, 270.0, 32.5, 21.0 ], 200 | "text" : "qlim" 201 | } 202 | 203 | } 204 | , { 205 | "box" : { 206 | "id" : "obj-9", 207 | "maxclass" : "button", 208 | "numinlets" : 1, 209 | "numoutlets" : 1, 210 | "outlettype" : [ "bang" ], 211 | "patching_rect" : [ 165.0, 210.0, 30.0, 30.0 ] 212 | } 213 | 214 | } 215 | , { 216 | "box" : { 217 | "fontname" : "Helvetica Neue Light", 218 | "fontsize" : 12.0, 219 | "id" : "obj-6", 220 | "maxclass" : "newobj", 221 | "numinlets" : 2, 222 | "numoutlets" : 1, 223 | "outlettype" : [ "int" ], 224 | "patching_rect" : [ 260.0, 480.0, 32.5, 21.0 ], 225 | "text" : "i" 226 | } 227 | 228 | } 229 | , { 230 | "box" : { 231 | "fontname" : "Helvetica Neue Light", 232 | "fontsize" : 12.0, 233 | "frgb" : 0.0, 234 | "id" : "obj-5", 235 | "maxclass" : "comment", 236 | "numinlets" : 1, 237 | "numoutlets" : 0, 238 | "patching_rect" : [ 20.0, 130.0, 280.0, 21.0 ], 239 | "text" : "An arg of 0 indicates to test for \"no error\"" 240 | } 241 | 242 | } 243 | , { 244 | "box" : { 245 | "fontname" : "Helvetica Neue Light", 246 | "fontsize" : 12.0, 247 | "frgb" : 0.0, 248 | "id" : "obj-4", 249 | "maxclass" : "comment", 250 | "numinlets" : 1, 251 | "numoutlets" : 0, 252 | "patching_rect" : [ 20.0, 110.0, 280.0, 21.0 ], 253 | "text" : "An arg of 1 indicates to test for \"error\"" 254 | } 255 | 256 | } 257 | , { 258 | "box" : { 259 | "comment" : "", 260 | "id" : "obj-3", 261 | "maxclass" : "inlet", 262 | "numinlets" : 0, 263 | "numoutlets" : 1, 264 | "outlettype" : [ "bang" ], 265 | "patching_rect" : [ 165.0, 175.0, 25.0, 25.0 ] 266 | } 267 | 268 | } 269 | , { 270 | "box" : { 271 | "fontname" : "Helvetica Neue Light", 272 | "fontsize" : 12.0, 273 | "frgb" : 0.0, 274 | "id" : "obj-2", 275 | "linecount" : 2, 276 | "maxclass" : "comment", 277 | "numinlets" : 1, 278 | "numoutlets" : 0, 279 | "patching_rect" : [ 20.0, 70.0, 201.0, 35.0 ], 280 | "text" : "This abstraction is for trapping errors for automated test purposes." 281 | } 282 | 283 | } 284 | , { 285 | "box" : { 286 | "fontname" : "Helvetica Neue Light", 287 | "fontsize" : 12.0, 288 | "frgb" : 0.0, 289 | "id" : "obj-86", 290 | "maxclass" : "comment", 291 | "numinlets" : 1, 292 | "numoutlets" : 0, 293 | "patching_rect" : [ 305.0, 510.0, 65.0, 21.0 ], 294 | "text" : "yes error" 295 | } 296 | 297 | } 298 | , { 299 | "box" : { 300 | "fontname" : "Helvetica Neue Light", 301 | "fontsize" : 12.0, 302 | "id" : "obj-82", 303 | "maxclass" : "newobj", 304 | "numinlets" : 2, 305 | "numoutlets" : 1, 306 | "outlettype" : [ "int" ], 307 | "patching_rect" : [ 260.0, 510.0, 42.0, 21.0 ], 308 | "text" : "== #1" 309 | } 310 | 311 | } 312 | ], 313 | "lines" : [ { 314 | "patchline" : { 315 | "destination" : [ "obj-13", 0 ], 316 | "disabled" : 0, 317 | "hidden" : 0, 318 | "source" : [ "obj-10", 0 ] 319 | } 320 | 321 | } 322 | , { 323 | "patchline" : { 324 | "destination" : [ "obj-14", 0 ], 325 | "disabled" : 0, 326 | "hidden" : 0, 327 | "source" : [ "obj-13", 0 ] 328 | } 329 | 330 | } 331 | , { 332 | "patchline" : { 333 | "destination" : [ "obj-23", 0 ], 334 | "disabled" : 0, 335 | "hidden" : 0, 336 | "source" : [ "obj-13", 2 ] 337 | } 338 | 339 | } 340 | , { 341 | "patchline" : { 342 | "destination" : [ "obj-6", 0 ], 343 | "disabled" : 0, 344 | "hidden" : 0, 345 | "source" : [ "obj-13", 1 ] 346 | } 347 | 348 | } 349 | , { 350 | "patchline" : { 351 | "destination" : [ "obj-26", 0 ], 352 | "disabled" : 0, 353 | "hidden" : 0, 354 | "source" : [ "obj-21", 0 ] 355 | } 356 | 357 | } 358 | , { 359 | "patchline" : { 360 | "destination" : [ "obj-21", 0 ], 361 | "disabled" : 0, 362 | "hidden" : 0, 363 | "source" : [ "obj-23", 0 ] 364 | } 365 | 366 | } 367 | , { 368 | "patchline" : { 369 | "destination" : [ "obj-6", 1 ], 370 | "disabled" : 0, 371 | "hidden" : 0, 372 | "source" : [ "obj-26", 0 ] 373 | } 374 | 375 | } 376 | , { 377 | "patchline" : { 378 | "destination" : [ "obj-23", 0 ], 379 | "disabled" : 0, 380 | "hidden" : 0, 381 | "source" : [ "obj-28", 0 ] 382 | } 383 | 384 | } 385 | , { 386 | "patchline" : { 387 | "destination" : [ "obj-9", 0 ], 388 | "disabled" : 0, 389 | "hidden" : 0, 390 | "source" : [ "obj-3", 0 ] 391 | } 392 | 393 | } 394 | , { 395 | "patchline" : { 396 | "destination" : [ "obj-82", 0 ], 397 | "disabled" : 0, 398 | "hidden" : 0, 399 | "source" : [ "obj-6", 0 ] 400 | } 401 | 402 | } 403 | , { 404 | "patchline" : { 405 | "destination" : [ "obj-6", 1 ], 406 | "disabled" : 0, 407 | "hidden" : 0, 408 | "source" : [ "obj-8", 0 ] 409 | } 410 | 411 | } 412 | , { 413 | "patchline" : { 414 | "destination" : [ "obj-17", 0 ], 415 | "disabled" : 0, 416 | "hidden" : 0, 417 | "source" : [ "obj-82", 0 ] 418 | } 419 | 420 | } 421 | , { 422 | "patchline" : { 423 | "destination" : [ "obj-10", 0 ], 424 | "disabled" : 0, 425 | "hidden" : 0, 426 | "source" : [ "obj-9", 0 ] 427 | } 428 | 429 | } 430 | , { 431 | "patchline" : { 432 | "destination" : [ "obj-28", 0 ], 433 | "disabled" : 0, 434 | "hidden" : 0, 435 | "source" : [ "obj-9", 0 ] 436 | } 437 | 438 | } 439 | , { 440 | "patchline" : { 441 | "destination" : [ "obj-8", 0 ], 442 | "disabled" : 0, 443 | "hidden" : 0, 444 | "source" : [ "obj-9", 0 ] 445 | } 446 | 447 | } 448 | ] 449 | } 450 | 451 | } 452 | -------------------------------------------------------------------------------- /misc/test.string.equals.js: -------------------------------------------------------------------------------- 1 | // compare two strings for equality 2 | 3 | inlets = 2; 4 | outlets = 1; 5 | 6 | var ref_string = ""; 7 | 8 | function anything() 9 | { 10 | var temp = messagename.replace(/\r/g, ''); 11 | 12 | if (inlet == 0) { 13 | if (temp == ref_string) 14 | outlet(0, 1); 15 | else 16 | outlet(0, 0); 17 | } 18 | else { 19 | ref_string = temp; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /misc/test.string.equals.maxpat: -------------------------------------------------------------------------------- 1 | { 2 | "patcher" : { 3 | "fileversion" : 1, 4 | "appversion" : { 5 | "major" : 8, 6 | "minor" : 0, 7 | "revision" : 4, 8 | "architecture" : "x64", 9 | "modernui" : 1 10 | } 11 | , 12 | "classnamespace" : "box", 13 | "rect" : [ 310.0, 210.0, 458.0, 294.0 ], 14 | "bglocked" : 0, 15 | "openinpresentation" : 0, 16 | "default_fontsize" : 12.0, 17 | "default_fontface" : 0, 18 | "default_fontname" : "Helvetica Neue Light", 19 | "gridonopen" : 1, 20 | "gridsize" : [ 5.0, 5.0 ], 21 | "gridsnaponopen" : 1, 22 | "objectsnaponopen" : 1, 23 | "statusbarvisible" : 2, 24 | "toolbarvisible" : 1, 25 | "lefttoolbarpinned" : 0, 26 | "toptoolbarpinned" : 0, 27 | "righttoolbarpinned" : 0, 28 | "bottomtoolbarpinned" : 0, 29 | "toolbars_unpinned_last_save" : 0, 30 | "tallnewobj" : 0, 31 | "boxanimatetime" : 200, 32 | "enablehscroll" : 1, 33 | "enablevscroll" : 1, 34 | "devicewidth" : 0.0, 35 | "description" : "", 36 | "digest" : "", 37 | "tags" : "", 38 | "style" : "", 39 | "subpatcher_template" : "", 40 | "boxes" : [ { 41 | "box" : { 42 | "id" : "obj-10", 43 | "maxclass" : "message", 44 | "numinlets" : 2, 45 | "numoutlets" : 1, 46 | "outlettype" : [ "" ], 47 | "patching_rect" : [ 200.0, 88.0, 29.5, 23.0 ], 48 | "text" : "$1" 49 | } 50 | 51 | } 52 | , { 53 | "box" : { 54 | "id" : "obj-5", 55 | "maxclass" : "newobj", 56 | "numinlets" : 1, 57 | "numoutlets" : 1, 58 | "outlettype" : [ "bang" ], 59 | "patching_rect" : [ 200.0, 22.0, 57.0, 23.0 ], 60 | "text" : "loadbang" 61 | } 62 | 63 | } 64 | , { 65 | "box" : { 66 | "id" : "obj-4", 67 | "maxclass" : "newobj", 68 | "numinlets" : 1, 69 | "numoutlets" : 2, 70 | "outlettype" : [ "", "" ], 71 | "patching_rect" : [ 200.0, 55.0, 70.0, 23.0 ], 72 | "text" : "patcherargs" 73 | } 74 | 75 | } 76 | , { 77 | "box" : { 78 | "fontname" : "Helvetica Neue Light", 79 | "fontsize" : 12.0, 80 | "id" : "obj-3", 81 | "maxclass" : "newobj", 82 | "numinlets" : 1, 83 | "numoutlets" : 1, 84 | "outlettype" : [ "" ], 85 | "patching_rect" : [ 116.0, 123.0, 58.0, 23.0 ], 86 | "text" : "tosymbol" 87 | } 88 | 89 | } 90 | , { 91 | "box" : { 92 | "fontname" : "Helvetica Neue Light", 93 | "fontsize" : 12.0, 94 | "id" : "obj-2", 95 | "maxclass" : "newobj", 96 | "numinlets" : 1, 97 | "numoutlets" : 1, 98 | "outlettype" : [ "" ], 99 | "patching_rect" : [ 46.0, 123.0, 58.0, 23.0 ], 100 | "text" : "tosymbol" 101 | } 102 | 103 | } 104 | , { 105 | "box" : { 106 | "comment" : "", 107 | "id" : "obj-12", 108 | "index" : 1, 109 | "maxclass" : "outlet", 110 | "numinlets" : 1, 111 | "numoutlets" : 0, 112 | "patching_rect" : [ 46.0, 188.0, 25.0, 25.0 ] 113 | } 114 | 115 | } 116 | , { 117 | "box" : { 118 | "comment" : "", 119 | "id" : "obj-11", 120 | "index" : 2, 121 | "maxclass" : "inlet", 122 | "numinlets" : 0, 123 | "numoutlets" : 1, 124 | "outlettype" : [ "" ], 125 | "patching_rect" : [ 146.0, 88.0, 25.0, 25.0 ] 126 | } 127 | 128 | } 129 | , { 130 | "box" : { 131 | "comment" : "", 132 | "id" : "obj-7", 133 | "index" : 1, 134 | "maxclass" : "inlet", 135 | "numinlets" : 0, 136 | "numoutlets" : 1, 137 | "outlettype" : [ "" ], 138 | "patching_rect" : [ 46.0, 88.0, 25.0, 25.0 ] 139 | } 140 | 141 | } 142 | , { 143 | "box" : { 144 | "fontname" : "Helvetica Neue Light", 145 | "fontsize" : 12.0, 146 | "id" : "obj-1", 147 | "maxclass" : "newobj", 148 | "numinlets" : 2, 149 | "numoutlets" : 1, 150 | "outlettype" : [ "" ], 151 | "patching_rect" : [ 46.0, 158.0, 121.0, 23.0 ], 152 | "saved_object_attributes" : { 153 | "filename" : "test.string.equals.js", 154 | "parameter_enable" : 0 155 | } 156 | , 157 | "text" : "js test.string.equals.js" 158 | } 159 | 160 | } 161 | ], 162 | "lines" : [ { 163 | "patchline" : { 164 | "destination" : [ "obj-12", 0 ], 165 | "source" : [ "obj-1", 0 ] 166 | } 167 | 168 | } 169 | , { 170 | "patchline" : { 171 | "destination" : [ "obj-3", 0 ], 172 | "source" : [ "obj-10", 0 ] 173 | } 174 | 175 | } 176 | , { 177 | "patchline" : { 178 | "destination" : [ "obj-3", 0 ], 179 | "source" : [ "obj-11", 0 ] 180 | } 181 | 182 | } 183 | , { 184 | "patchline" : { 185 | "destination" : [ "obj-1", 0 ], 186 | "source" : [ "obj-2", 0 ] 187 | } 188 | 189 | } 190 | , { 191 | "patchline" : { 192 | "destination" : [ "obj-1", 1 ], 193 | "source" : [ "obj-3", 0 ] 194 | } 195 | 196 | } 197 | , { 198 | "patchline" : { 199 | "destination" : [ "obj-10", 0 ], 200 | "source" : [ "obj-4", 0 ] 201 | } 202 | 203 | } 204 | , { 205 | "patchline" : { 206 | "destination" : [ "obj-4", 0 ], 207 | "source" : [ "obj-5", 0 ] 208 | } 209 | 210 | } 211 | , { 212 | "patchline" : { 213 | "destination" : [ "obj-2", 0 ], 214 | "source" : [ "obj-7", 0 ] 215 | } 216 | 217 | } 218 | ] 219 | } 220 | 221 | } 222 | -------------------------------------------------------------------------------- /package-info.json.in: -------------------------------------------------------------------------------- 1 | { 2 | "author" : "Cycling '74", 3 | "description" : "Tools for creating and running test patchers with Max.", 4 | "homepatcher" : "oscar.maxhelp", 5 | "max_version_min" : "8.2", 6 | "max_version_max" : "none", 7 | "name" : "sampler", 8 | "os" : { 9 | "macintosh" : { 10 | "platform" : [ "ia32", "x64", "aarch64" ], 11 | "min_version" : "none" 12 | }, 13 | "windows" : { 14 | "platform" : [ "ia32", "x64" ], 15 | "min_version" : "none" 16 | } 17 | }, 18 | "package_extra" : { 19 | "reverse_domain" : "com.cycling74", 20 | "copyright" : "Copyright (c) 2019 Cycling '74", 21 | "forcerestart" : 1 22 | }, 23 | "tags" : [ ], 24 | "version" : "1.2.1", 25 | "website" : "http://www.github.com/Cycling74/max-test" 26 | } 27 | -------------------------------------------------------------------------------- /patchers/2775-scale~.maxtest.maxpat: -------------------------------------------------------------------------------- 1 | { 2 | "patcher" : { 3 | "fileversion" : 1, 4 | "appversion" : { 5 | "major" : 6, 6 | "minor" : 1, 7 | "revision" : 7, 8 | "architecture" : "x86" 9 | } 10 | , 11 | "rect" : [ 25.0, 69.0, 451.0, 449.0 ], 12 | "bglocked" : 0, 13 | "openinpresentation" : 0, 14 | "default_fontsize" : 12.0, 15 | "default_fontface" : 0, 16 | "default_fontname" : "Arial", 17 | "gridonopen" : 0, 18 | "gridsize" : [ 5.0, 5.0 ], 19 | "gridsnaponopen" : 0, 20 | "statusbarvisible" : 2, 21 | "toolbarvisible" : 1, 22 | "boxanimatetime" : 200, 23 | "imprint" : 0, 24 | "enablehscroll" : 1, 25 | "enablevscroll" : 1, 26 | "devicewidth" : 0.0, 27 | "description" : "", 28 | "digest" : "", 29 | "tags" : "", 30 | "boxes" : [ { 31 | "box" : { 32 | "fontname" : "Arial", 33 | "fontsize" : 12.0, 34 | "id" : "obj-19", 35 | "maxclass" : "newobj", 36 | "numinlets" : 1, 37 | "numoutlets" : 2, 38 | "outlettype" : [ "bang", "float" ], 39 | "patching_rect" : [ 20.0, 275.0, 32.5, 20.0 ], 40 | "text" : "t b f" 41 | } 42 | 43 | } 44 | , { 45 | "box" : { 46 | "fontname" : "Arial", 47 | "fontsize" : 12.0, 48 | "id" : "obj-7", 49 | "maxclass" : "flonum", 50 | "numinlets" : 1, 51 | "numoutlets" : 2, 52 | "outlettype" : [ "float", "bang" ], 53 | "parameter_enable" : 0, 54 | "patching_rect" : [ 20.0, 250.0, 115.0, 20.0 ] 55 | } 56 | 57 | } 58 | , { 59 | "box" : { 60 | "color" : [ 1.0, 0.66, 0.0, 1.0 ], 61 | "fontname" : "Arial", 62 | "fontsize" : 12.0, 63 | "id" : "obj-5", 64 | "maxclass" : "newobj", 65 | "numinlets" : 1, 66 | "numoutlets" : 1, 67 | "outlettype" : [ "" ], 68 | "patching_rect" : [ 20.0, 225.0, 79.0, 20.0 ], 69 | "text" : "test.sample~" 70 | } 71 | 72 | } 73 | , { 74 | "box" : { 75 | "fontname" : "Arial", 76 | "fontsize" : 12.0, 77 | "frgb" : 0.0, 78 | "id" : "obj-23", 79 | "maxclass" : "comment", 80 | "numinlets" : 1, 81 | "numoutlets" : 0, 82 | "patching_rect" : [ 19.0, 23.0, 395.0, 20.0 ], 83 | "text" : "compares the output of scale and scale~ when input ranges are negative" 84 | } 85 | 86 | } 87 | , { 88 | "box" : { 89 | "id" : "obj-20", 90 | "maxclass" : "toggle", 91 | "numinlets" : 1, 92 | "numoutlets" : 1, 93 | "outlettype" : [ "int" ], 94 | "parameter_enable" : 0, 95 | "patching_rect" : [ 111.0, 353.0, 20.0, 20.0 ] 96 | } 97 | 98 | } 99 | , { 100 | "box" : { 101 | "fontname" : "Arial", 102 | "fontsize" : 12.0, 103 | "id" : "obj-17", 104 | "maxclass" : "newobj", 105 | "numinlets" : 2, 106 | "numoutlets" : 0, 107 | "patching_rect" : [ 65.0, 100.0, 37.0, 20.0 ], 108 | "text" : "dac~" 109 | } 110 | 111 | } 112 | , { 113 | "box" : { 114 | "color" : [ 1.0, 0.66, 0.0, 1.0 ], 115 | "fontname" : "Arial", 116 | "fontsize" : 12.0, 117 | "id" : "obj-16", 118 | "maxclass" : "newobj", 119 | "numinlets" : 1, 120 | "numoutlets" : 0, 121 | "patching_rect" : [ 20.0, 385.0, 83.0, 20.0 ], 122 | "text" : "test.terminate" 123 | } 124 | 125 | } 126 | , { 127 | "box" : { 128 | "fontname" : "Arial", 129 | "fontsize" : 12.0, 130 | "id" : "obj-15", 131 | "maxclass" : "newobj", 132 | "numinlets" : 1, 133 | "numoutlets" : 2, 134 | "outlettype" : [ "float", "int" ], 135 | "patching_rect" : [ 20.0, 75.0, 64.0, 20.0 ], 136 | "text" : "t -22.5 1" 137 | } 138 | 139 | } 140 | , { 141 | "box" : { 142 | "fontname" : "Arial", 143 | "fontsize" : 12.0, 144 | "id" : "obj-13", 145 | "maxclass" : "newobj", 146 | "numinlets" : 1, 147 | "numoutlets" : 1, 148 | "outlettype" : [ "bang" ], 149 | "patching_rect" : [ 20.0, 45.0, 60.0, 20.0 ], 150 | "text" : "loadbang" 151 | } 152 | 153 | } 154 | , { 155 | "box" : { 156 | "color" : [ 1.0, 0.66, 0.0, 1.0 ], 157 | "fontname" : "Arial", 158 | "fontsize" : 12.0, 159 | "id" : "obj-11", 160 | "maxclass" : "newobj", 161 | "numinlets" : 1, 162 | "numoutlets" : 1, 163 | "outlettype" : [ "" ], 164 | "patching_rect" : [ 111.0, 385.0, 105.0, 20.0 ], 165 | "text" : "test.assert equals" 166 | } 167 | 168 | } 169 | , { 170 | "box" : { 171 | "color" : [ 1.0, 0.66, 0.0, 1.0 ], 172 | "fontname" : "Arial", 173 | "fontsize" : 12.0, 174 | "id" : "obj-8", 175 | "maxclass" : "newobj", 176 | "numinlets" : 2, 177 | "numoutlets" : 1, 178 | "outlettype" : [ "" ], 179 | "patching_rect" : [ 111.0, 321.0, 69.0, 20.0 ], 180 | "text" : "test.equals" 181 | } 182 | 183 | } 184 | , { 185 | "box" : { 186 | "fontname" : "Arial", 187 | "fontsize" : 12.0, 188 | "id" : "obj-18", 189 | "maxclass" : "newobj", 190 | "numinlets" : 1, 191 | "numoutlets" : 1, 192 | "outlettype" : [ "signal" ], 193 | "patching_rect" : [ 20.0, 175.0, 33.0, 20.0 ], 194 | "text" : "sig~" 195 | } 196 | 197 | } 198 | , { 199 | "box" : { 200 | "fontname" : "Arial", 201 | "fontsize" : 12.0, 202 | "id" : "obj-14", 203 | "maxclass" : "flonum", 204 | "numinlets" : 1, 205 | "numoutlets" : 2, 206 | "outlettype" : [ "float", "bang" ], 207 | "parameter_enable" : 0, 208 | "patching_rect" : [ 210.0, 234.0, 50.0, 20.0 ] 209 | } 210 | 211 | } 212 | , { 213 | "box" : { 214 | "fontname" : "Arial", 215 | "fontsize" : 12.0, 216 | "id" : "obj-12", 217 | "maxclass" : "flonum", 218 | "numinlets" : 1, 219 | "numoutlets" : 2, 220 | "outlettype" : [ "float", "bang" ], 221 | "parameter_enable" : 0, 222 | "patching_rect" : [ 20.0, 140.0, 50.0, 20.0 ] 223 | } 224 | 225 | } 226 | , { 227 | "box" : { 228 | "fontname" : "Arial", 229 | "fontsize" : 12.0, 230 | "id" : "obj-10", 231 | "maxclass" : "newobj", 232 | "numinlets" : 6, 233 | "numoutlets" : 1, 234 | "outlettype" : [ "" ], 235 | "patching_rect" : [ 210.0, 200.0, 120.0, 20.0 ], 236 | "text" : "scale -21.5 -22.5 0 1" 237 | } 238 | 239 | } 240 | , { 241 | "box" : { 242 | "fontname" : "Arial", 243 | "fontsize" : 12.0, 244 | "id" : "obj-1", 245 | "maxclass" : "newobj", 246 | "numinlets" : 6, 247 | "numoutlets" : 1, 248 | "outlettype" : [ "signal" ], 249 | "patching_rect" : [ 20.0, 200.0, 127.0, 20.0 ], 250 | "text" : "scale~ -21.5 -22.5 0 1" 251 | } 252 | 253 | } 254 | ], 255 | "lines" : [ { 256 | "patchline" : { 257 | "destination" : [ "obj-5", 0 ], 258 | "disabled" : 0, 259 | "hidden" : 0, 260 | "source" : [ "obj-1", 0 ] 261 | } 262 | 263 | } 264 | , { 265 | "patchline" : { 266 | "destination" : [ "obj-14", 0 ], 267 | "disabled" : 0, 268 | "hidden" : 0, 269 | "source" : [ "obj-10", 0 ] 270 | } 271 | 272 | } 273 | , { 274 | "patchline" : { 275 | "destination" : [ "obj-10", 0 ], 276 | "disabled" : 0, 277 | "hidden" : 0, 278 | "source" : [ "obj-12", 0 ] 279 | } 280 | 281 | } 282 | , { 283 | "patchline" : { 284 | "destination" : [ "obj-18", 0 ], 285 | "disabled" : 0, 286 | "hidden" : 0, 287 | "source" : [ "obj-12", 0 ] 288 | } 289 | 290 | } 291 | , { 292 | "patchline" : { 293 | "destination" : [ "obj-15", 0 ], 294 | "disabled" : 0, 295 | "hidden" : 0, 296 | "source" : [ "obj-13", 0 ] 297 | } 298 | 299 | } 300 | , { 301 | "patchline" : { 302 | "destination" : [ "obj-8", 1 ], 303 | "disabled" : 0, 304 | "hidden" : 0, 305 | "source" : [ "obj-14", 0 ] 306 | } 307 | 308 | } 309 | , { 310 | "patchline" : { 311 | "destination" : [ "obj-12", 0 ], 312 | "disabled" : 0, 313 | "hidden" : 0, 314 | "source" : [ "obj-15", 0 ] 315 | } 316 | 317 | } 318 | , { 319 | "patchline" : { 320 | "destination" : [ "obj-17", 0 ], 321 | "disabled" : 0, 322 | "hidden" : 0, 323 | "source" : [ "obj-15", 1 ] 324 | } 325 | 326 | } 327 | , { 328 | "patchline" : { 329 | "destination" : [ "obj-1", 0 ], 330 | "disabled" : 0, 331 | "hidden" : 0, 332 | "source" : [ "obj-18", 0 ] 333 | } 334 | 335 | } 336 | , { 337 | "patchline" : { 338 | "destination" : [ "obj-16", 0 ], 339 | "disabled" : 0, 340 | "hidden" : 0, 341 | "source" : [ "obj-19", 0 ] 342 | } 343 | 344 | } 345 | , { 346 | "patchline" : { 347 | "destination" : [ "obj-8", 0 ], 348 | "disabled" : 0, 349 | "hidden" : 0, 350 | "source" : [ "obj-19", 1 ] 351 | } 352 | 353 | } 354 | , { 355 | "patchline" : { 356 | "destination" : [ "obj-11", 0 ], 357 | "disabled" : 0, 358 | "hidden" : 0, 359 | "source" : [ "obj-20", 0 ] 360 | } 361 | 362 | } 363 | , { 364 | "patchline" : { 365 | "destination" : [ "obj-7", 0 ], 366 | "disabled" : 0, 367 | "hidden" : 0, 368 | "source" : [ "obj-5", 0 ] 369 | } 370 | 371 | } 372 | , { 373 | "patchline" : { 374 | "destination" : [ "obj-19", 0 ], 375 | "disabled" : 0, 376 | "hidden" : 0, 377 | "source" : [ "obj-7", 0 ] 378 | } 379 | 380 | } 381 | , { 382 | "patchline" : { 383 | "destination" : [ "obj-20", 0 ], 384 | "disabled" : 0, 385 | "hidden" : 0, 386 | "source" : [ "obj-8", 0 ] 387 | } 388 | 389 | } 390 | ], 391 | "dependency_cache" : [ { 392 | "name" : "oscar.mxo", 393 | "type" : "iLaX" 394 | } 395 | , { 396 | "name" : "oscar.mxo", 397 | "type" : "iLaX" 398 | } 399 | , { 400 | "name" : "oscar.mxo", 401 | "type" : "iLaX" 402 | } 403 | , { 404 | "name" : "oscar.mxo", 405 | "type" : "iLaX" 406 | } 407 | ] 408 | } 409 | 410 | } 411 | -------------------------------------------------------------------------------- /patchers/2779-3314-allpass~.impulse-response.aif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cycling74/max-test/7097a11b31e9dc93ad47a6717feab04605799275/patchers/2779-3314-allpass~.impulse-response.aif -------------------------------------------------------------------------------- /patchers/4505-dict-notification-js.maxtest.maxzip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cycling74/max-test/7097a11b31e9dc93ad47a6717feab04605799275/patchers/4505-dict-notification-js.maxtest.maxzip -------------------------------------------------------------------------------- /patchers/console-has-error.maxtest.maxpat: -------------------------------------------------------------------------------- 1 | { 2 | "patcher" : { 3 | "fileversion" : 1, 4 | "appversion" : { 5 | "major" : 7, 6 | "minor" : 2, 7 | "revision" : 5, 8 | "architecture" : "x86", 9 | "modernui" : 1 10 | } 11 | , 12 | "rect" : [ 248.0, 291.0, 461.0, 415.0 ], 13 | "bgcolor" : [ 0.239216, 0.254902, 0.278431, 1.0 ], 14 | "editing_bgcolor" : [ 0.32549, 0.345098, 0.372549, 1.0 ], 15 | "bglocked" : 0, 16 | "openinpresentation" : 0, 17 | "default_fontsize" : 12.0, 18 | "default_fontface" : 0, 19 | "default_fontname" : "Courier", 20 | "gridonopen" : 2, 21 | "gridsize" : [ 15.0, 5.0 ], 22 | "gridsnaponopen" : 2, 23 | "objectsnaponopen" : 0, 24 | "statusbarvisible" : 2, 25 | "toolbarvisible" : 1, 26 | "lefttoolbarpinned" : 0, 27 | "toptoolbarpinned" : 0, 28 | "righttoolbarpinned" : 0, 29 | "bottomtoolbarpinned" : 0, 30 | "toolbars_unpinned_last_save" : 0, 31 | "tallnewobj" : 0, 32 | "boxanimatetime" : 200, 33 | "enablehscroll" : 1, 34 | "enablevscroll" : 1, 35 | "devicewidth" : 0.0, 36 | "description" : "", 37 | "digest" : "", 38 | "tags" : "", 39 | "style" : "cassiel", 40 | "subpatcher_template" : "cassiel", 41 | "boxes" : [ { 42 | "box" : { 43 | "color" : [ 0.7, 0.4, 0.3, 1.0 ], 44 | "id" : "obj-10", 45 | "maxclass" : "newobj", 46 | "numinlets" : 1, 47 | "numoutlets" : 0, 48 | "patching_rect" : [ 45.0, 295.0, 111.0, 20.0 ], 49 | "style" : "", 50 | "text" : "test.terminate" 51 | } 52 | 53 | } 54 | , { 55 | "box" : { 56 | "id" : "obj-8", 57 | "maxclass" : "newobj", 58 | "numinlets" : 1, 59 | "numoutlets" : 2, 60 | "outlettype" : [ "bang", "bang" ], 61 | "patching_rect" : [ 135.0, 95.0, 32.0, 20.0 ], 62 | "style" : "", 63 | "text" : "b 2" 64 | } 65 | 66 | } 67 | , { 68 | "box" : { 69 | "id" : "obj-7", 70 | "maxclass" : "message", 71 | "numinlets" : 2, 72 | "numoutlets" : 1, 73 | "outlettype" : [ "" ], 74 | "patching_rect" : [ 255.0, 160.0, 176.0, 20.0 ], 75 | "style" : "", 76 | "text" : "won't-understand-BOOGLE" 77 | } 78 | 79 | } 80 | , { 81 | "box" : { 82 | "id" : "obj-5", 83 | "maxclass" : "number", 84 | "numinlets" : 1, 85 | "numoutlets" : 2, 86 | "outlettype" : [ "", "bang" ], 87 | "parameter_enable" : 0, 88 | "patching_rect" : [ 255.0, 195.0, 50.0, 20.0 ], 89 | "style" : "" 90 | } 91 | 92 | } 93 | , { 94 | "box" : { 95 | "id" : "obj-2", 96 | "maxclass" : "newobj", 97 | "numinlets" : 1, 98 | "numoutlets" : 1, 99 | "outlettype" : [ "" ], 100 | "patching_rect" : [ 45.0, 260.0, 335.0, 20.0 ], 101 | "style" : "cassiel.ruby", 102 | "text" : "CheckConsoleHasError console-has-error BOOGLE" 103 | } 104 | 105 | } 106 | , { 107 | "box" : { 108 | "id" : "obj-1", 109 | "maxclass" : "newobj", 110 | "numinlets" : 1, 111 | "numoutlets" : 1, 112 | "outlettype" : [ "bang" ], 113 | "patching_rect" : [ 135.0, 60.0, 68.0, 20.0 ], 114 | "style" : "", 115 | "text" : "loadbang" 116 | } 117 | 118 | } 119 | ], 120 | "lines" : [ { 121 | "patchline" : { 122 | "destination" : [ "obj-8", 0 ], 123 | "disabled" : 0, 124 | "hidden" : 0, 125 | "source" : [ "obj-1", 0 ] 126 | } 127 | 128 | } 129 | , { 130 | "patchline" : { 131 | "destination" : [ "obj-10", 0 ], 132 | "disabled" : 0, 133 | "hidden" : 0, 134 | "source" : [ "obj-2", 0 ] 135 | } 136 | 137 | } 138 | , { 139 | "patchline" : { 140 | "destination" : [ "obj-5", 0 ], 141 | "disabled" : 0, 142 | "hidden" : 0, 143 | "source" : [ "obj-7", 0 ] 144 | } 145 | 146 | } 147 | , { 148 | "patchline" : { 149 | "destination" : [ "obj-2", 0 ], 150 | "disabled" : 0, 151 | "hidden" : 0, 152 | "source" : [ "obj-8", 0 ] 153 | } 154 | 155 | } 156 | , { 157 | "patchline" : { 158 | "destination" : [ "obj-7", 0 ], 159 | "disabled" : 0, 160 | "hidden" : 0, 161 | "source" : [ "obj-8", 1 ] 162 | } 163 | 164 | } 165 | ], 166 | "dependency_cache" : [ { 167 | "name" : "CheckConsoleHasError.maxpat", 168 | "bootpath" : "~/Documents/Max 7/Packages/max-test/patchers/lib", 169 | "type" : "JSON", 170 | "implicit" : 1 171 | } 172 | , { 173 | "name" : "oscar.mxo", 174 | "type" : "iLaX" 175 | } 176 | , { 177 | "name" : "oscar.mxo", 178 | "type" : "iLaX" 179 | } 180 | , { 181 | "name" : "oscar.mxo", 182 | "type" : "iLaX" 183 | } 184 | ], 185 | "autosave" : 0, 186 | "styles" : [ { 187 | "name" : "cassiel", 188 | "default" : { 189 | "bgfillcolor" : { 190 | "type" : "gradient", 191 | "color" : [ 0.290196, 0.309804, 0.301961, 1.0 ], 192 | "color1" : [ 0.486435, 0.462784, 0.5, 1.0 ], 193 | "color2" : [ 0.19771, 0.188048, 0.201856, 1.0 ], 194 | "angle" : 270.0, 195 | "proportion" : 0.39, 196 | "autogradient" : 0 197 | } 198 | , 199 | "bgcolor" : [ 0.095481, 0.100396, 0.100293, 1.0 ], 200 | "color" : [ 1.0, 1.0, 1.0, 1.0 ], 201 | "elementcolor" : [ 0.32549, 0.345098, 0.372549, 1.0 ], 202 | "selectioncolor" : [ 0.784314, 0.145098, 0.023529, 1.0 ], 203 | "patchlinecolor" : [ 0.960784, 0.827451, 0.156863, 0.9 ], 204 | "fontname" : [ "Courier" ] 205 | } 206 | , 207 | "parentstyle" : "", 208 | "multi" : 0 209 | } 210 | , { 211 | "name" : "cassiel.ruby", 212 | "default" : { 213 | "textcolor_inverse" : [ 0.784314, 0.145098, 0.023529, 1.0 ], 214 | "accentcolor" : [ 0.341176, 0.027451, 0.023529, 0.5 ] 215 | } 216 | , 217 | "parentstyle" : "", 218 | "multi" : 0 219 | } 220 | , { 221 | "name" : "foo-style", 222 | "default" : { 223 | "color" : [ 0.720698, 0.16723, 0.080014, 1.0 ], 224 | "elementcolor" : [ 0.836576, 0.903148, 0.643029, 1.0 ], 225 | "fontname" : [ "Courier" ] 226 | } 227 | , 228 | "parentstyle" : "", 229 | "multi" : 0 230 | } 231 | , { 232 | "name" : "sky-blue", 233 | "default" : { 234 | "bgcolor" : [ 0.670588, 0.74902, 0.807843, 1.0 ], 235 | "textcolor_inverse" : [ 0.239216, 0.254902, 0.278431, 1.0 ], 236 | "color" : [ 0.095481, 0.100396, 0.100293, 1.0 ], 237 | "elementcolor" : [ 0.792189, 0.848618, 0.854853, 1.0 ], 238 | "selectioncolor" : [ 0.870588, 0.415686, 0.062745, 1.0 ], 239 | "fontname" : [ "Courier" ] 240 | } 241 | , 242 | "parentstyle" : "default", 243 | "multi" : 0 244 | } 245 | ], 246 | "default_bgcolor" : [ 0.095481, 0.100396, 0.100293, 1.0 ], 247 | "color" : [ 1.0, 1.0, 1.0, 1.0 ], 248 | "elementcolor" : [ 0.32549, 0.345098, 0.372549, 1.0 ], 249 | "selectioncolor" : [ 0.784314, 0.145098, 0.023529, 1.0 ], 250 | "patchlinecolor" : [ 0.960784, 0.827451, 0.156863, 0.9 ], 251 | "bgfillcolor_type" : "gradient", 252 | "bgfillcolor_color1" : [ 0.486435, 0.462784, 0.5, 1.0 ], 253 | "bgfillcolor_color2" : [ 0.19771, 0.188048, 0.201856, 1.0 ], 254 | "bgfillcolor_color" : [ 0.290196, 0.309804, 0.301961, 1.0 ], 255 | "bgfillcolor_angle" : 270.0, 256 | "bgfillcolor_proportion" : 0.39 257 | } 258 | 259 | } 260 | -------------------------------------------------------------------------------- /patchers/lib/CheckConsoleClear.maxpat: -------------------------------------------------------------------------------- 1 | { 2 | "patcher" : { 3 | "fileversion" : 1, 4 | "appversion" : { 5 | "major" : 7, 6 | "minor" : 2, 7 | "revision" : 5, 8 | "architecture" : "x64", 9 | "modernui" : 1 10 | } 11 | , 12 | "rect" : [ 549.0, 186.0, 789.0, 533.0 ], 13 | "bgcolor" : [ 0.239216, 0.254902, 0.278431, 1.0 ], 14 | "editing_bgcolor" : [ 0.32549, 0.345098, 0.372549, 1.0 ], 15 | "bglocked" : 0, 16 | "openinpresentation" : 0, 17 | "default_fontsize" : 12.0, 18 | "default_fontface" : 0, 19 | "default_fontname" : "Courier", 20 | "gridonopen" : 2, 21 | "gridsize" : [ 15.0, 5.0 ], 22 | "gridsnaponopen" : 2, 23 | "objectsnaponopen" : 0, 24 | "statusbarvisible" : 2, 25 | "toolbarvisible" : 1, 26 | "lefttoolbarpinned" : 0, 27 | "toptoolbarpinned" : 0, 28 | "righttoolbarpinned" : 0, 29 | "bottomtoolbarpinned" : 0, 30 | "toolbars_unpinned_last_save" : 0, 31 | "tallnewobj" : 0, 32 | "boxanimatetime" : 200, 33 | "enablehscroll" : 1, 34 | "enablevscroll" : 1, 35 | "devicewidth" : 0.0, 36 | "description" : "", 37 | "digest" : "", 38 | "tags" : "", 39 | "style" : "cassiel", 40 | "subpatcher_template" : "cassiel", 41 | "boxes" : [ { 42 | "box" : { 43 | "comment" : "bang when checked", 44 | "id" : "obj-1", 45 | "maxclass" : "outlet", 46 | "numinlets" : 1, 47 | "numoutlets" : 0, 48 | "patching_rect" : [ 420.0, 420.0, 30.0, 30.0 ], 49 | "presentation_rect" : [ 413.0, 418.0, 0.0, 0.0 ], 50 | "style" : "" 51 | } 52 | 53 | } 54 | , { 55 | "box" : { 56 | "id" : "obj-16", 57 | "maxclass" : "comment", 58 | "numinlets" : 1, 59 | "numoutlets" : 0, 60 | "patching_rect" : [ 360.0, 20.0, 150.0, 18.0 ], 61 | "style" : "cassiel.comment", 62 | "text" : "#1 = test name" 63 | } 64 | 65 | } 66 | , { 67 | "box" : { 68 | "comment" : "bang when checked", 69 | "id" : "obj-14", 70 | "maxclass" : "outlet", 71 | "numinlets" : 1, 72 | "numoutlets" : 0, 73 | "patching_rect" : [ 75.0, 420.0, 30.0, 30.0 ], 74 | "style" : "" 75 | } 76 | 77 | } 78 | , { 79 | "box" : { 80 | "id" : "obj-15", 81 | "maxclass" : "message", 82 | "numinlets" : 2, 83 | "numoutlets" : 1, 84 | "outlettype" : [ "" ], 85 | "patching_rect" : [ 225.0, 245.0, 54.0, 20.0 ], 86 | "style" : "", 87 | "text" : "set $1" 88 | } 89 | 90 | } 91 | , { 92 | "box" : { 93 | "id" : "obj-9", 94 | "maxclass" : "newobj", 95 | "numinlets" : 2, 96 | "numoutlets" : 1, 97 | "outlettype" : [ "" ], 98 | "patching_rect" : [ 150.0, 230.0, 39.0, 20.0 ], 99 | "style" : "", 100 | "text" : "qlim" 101 | } 102 | 103 | } 104 | , { 105 | "box" : { 106 | "id" : "obj-6", 107 | "maxclass" : "button", 108 | "numinlets" : 1, 109 | "numoutlets" : 1, 110 | "outlettype" : [ "bang" ], 111 | "patching_rect" : [ 150.0, 175.0, 24.0, 24.0 ], 112 | "style" : "" 113 | } 114 | 115 | } 116 | , { 117 | "box" : { 118 | "comment" : "bang: check total", 119 | "id" : "obj-2", 120 | "maxclass" : "inlet", 121 | "numinlets" : 0, 122 | "numoutlets" : 1, 123 | "outlettype" : [ "" ], 124 | "patching_rect" : [ 150.0, 125.0, 30.0, 30.0 ], 125 | "style" : "" 126 | } 127 | 128 | } 129 | , { 130 | "box" : { 131 | "id" : "obj-12", 132 | "maxclass" : "newobj", 133 | "numinlets" : 2, 134 | "numoutlets" : 1, 135 | "outlettype" : [ "int" ], 136 | "patching_rect" : [ 225.0, 330.0, 37.0, 20.0 ], 137 | "style" : "", 138 | "text" : "== 0" 139 | } 140 | 141 | } 142 | , { 143 | "box" : { 144 | "color" : [ 0.7, 0.4, 0.3, 1.0 ], 145 | "id" : "obj-11", 146 | "maxclass" : "newobj", 147 | "numinlets" : 1, 148 | "numoutlets" : 0, 149 | "patching_rect" : [ 225.0, 365.0, 169.0, 20.0 ], 150 | "style" : "", 151 | "text" : "test.assert #1:Console" 152 | } 153 | 154 | } 155 | , { 156 | "box" : { 157 | "id" : "obj-10", 158 | "maxclass" : "number", 159 | "numinlets" : 1, 160 | "numoutlets" : 2, 161 | "outlettype" : [ "", "bang" ], 162 | "parameter_enable" : 0, 163 | "patching_rect" : [ 225.0, 300.0, 50.0, 20.0 ], 164 | "style" : "" 165 | } 166 | 167 | } 168 | , { 169 | "box" : { 170 | "id" : "obj-8", 171 | "maxclass" : "newobj", 172 | "numinlets" : 5, 173 | "numoutlets" : 4, 174 | "outlettype" : [ "int", "", "", "int" ], 175 | "patching_rect" : [ 225.0, 200.0, 118.0, 20.0 ], 176 | "style" : "", 177 | "text" : "counter 1 32000" 178 | } 179 | 180 | } 181 | , { 182 | "box" : { 183 | "id" : "obj-7", 184 | "maxclass" : "button", 185 | "numinlets" : 1, 186 | "numoutlets" : 1, 187 | "outlettype" : [ "bang" ], 188 | "patching_rect" : [ 225.0, 170.0, 24.0, 24.0 ], 189 | "style" : "" 190 | } 191 | 192 | } 193 | , { 194 | "box" : { 195 | "color" : [ 0.7, 0.4, 0.3, 1.0 ], 196 | "id" : "obj-4", 197 | "maxclass" : "newobj", 198 | "numinlets" : 1, 199 | "numoutlets" : 0, 200 | "patching_rect" : [ 405.0, 320.0, 191.0, 20.0 ], 201 | "style" : "", 202 | "text" : "test.log #1:ConsoleErrors" 203 | } 204 | 205 | } 206 | , { 207 | "box" : { 208 | "id" : "obj-3", 209 | "maxclass" : "newobj", 210 | "numinlets" : 1, 211 | "numoutlets" : 1, 212 | "outlettype" : [ "" ], 213 | "patching_rect" : [ 360.0, 130.0, 60.0, 20.0 ], 214 | "style" : "", 215 | "text" : "error 1" 216 | } 217 | 218 | } 219 | ], 220 | "lines" : [ { 221 | "patchline" : { 222 | "destination" : [ "obj-12", 0 ], 223 | "disabled" : 0, 224 | "hidden" : 0, 225 | "source" : [ "obj-10", 0 ] 226 | } 227 | 228 | } 229 | , { 230 | "patchline" : { 231 | "destination" : [ "obj-1", 0 ], 232 | "disabled" : 0, 233 | "hidden" : 0, 234 | "source" : [ "obj-12", 0 ] 235 | } 236 | 237 | } 238 | , { 239 | "patchline" : { 240 | "destination" : [ "obj-11", 0 ], 241 | "disabled" : 0, 242 | "hidden" : 0, 243 | "source" : [ "obj-12", 0 ] 244 | } 245 | 246 | } 247 | , { 248 | "patchline" : { 249 | "destination" : [ "obj-10", 0 ], 250 | "disabled" : 0, 251 | "hidden" : 0, 252 | "source" : [ "obj-15", 0 ] 253 | } 254 | 255 | } 256 | , { 257 | "patchline" : { 258 | "destination" : [ "obj-6", 0 ], 259 | "disabled" : 0, 260 | "hidden" : 0, 261 | "source" : [ "obj-2", 0 ] 262 | } 263 | 264 | } 265 | , { 266 | "patchline" : { 267 | "destination" : [ "obj-4", 0 ], 268 | "disabled" : 0, 269 | "hidden" : 0, 270 | "source" : [ "obj-3", 0 ] 271 | } 272 | 273 | } 274 | , { 275 | "patchline" : { 276 | "destination" : [ "obj-7", 0 ], 277 | "disabled" : 0, 278 | "hidden" : 0, 279 | "source" : [ "obj-3", 0 ] 280 | } 281 | 282 | } 283 | , { 284 | "patchline" : { 285 | "destination" : [ "obj-9", 0 ], 286 | "disabled" : 0, 287 | "hidden" : 0, 288 | "source" : [ "obj-6", 0 ] 289 | } 290 | 291 | } 292 | , { 293 | "patchline" : { 294 | "destination" : [ "obj-8", 0 ], 295 | "disabled" : 0, 296 | "hidden" : 0, 297 | "source" : [ "obj-7", 0 ] 298 | } 299 | 300 | } 301 | , { 302 | "patchline" : { 303 | "destination" : [ "obj-15", 0 ], 304 | "disabled" : 0, 305 | "hidden" : 0, 306 | "source" : [ "obj-8", 0 ] 307 | } 308 | 309 | } 310 | , { 311 | "patchline" : { 312 | "destination" : [ "obj-10", 0 ], 313 | "disabled" : 0, 314 | "hidden" : 0, 315 | "source" : [ "obj-9", 0 ] 316 | } 317 | 318 | } 319 | , { 320 | "patchline" : { 321 | "destination" : [ "obj-14", 0 ], 322 | "disabled" : 0, 323 | "hidden" : 0, 324 | "source" : [ "obj-9", 0 ] 325 | } 326 | 327 | } 328 | ], 329 | "dependency_cache" : [ ], 330 | "autosave" : 0, 331 | "styles" : [ { 332 | "name" : "cassiel", 333 | "default" : { 334 | "fontname" : [ "Courier" ], 335 | "bgcolor" : [ 0.095481, 0.100396, 0.100293, 1.0 ], 336 | "patchlinecolor" : [ 0.960784, 0.827451, 0.156863, 0.9 ], 337 | "color" : [ 1.0, 1.0, 1.0, 1.0 ], 338 | "elementcolor" : [ 0.32549, 0.345098, 0.372549, 1.0 ], 339 | "bgfillcolor" : { 340 | "type" : "gradient", 341 | "color" : [ 0.290196, 0.309804, 0.301961, 1.0 ], 342 | "color1" : [ 0.486435, 0.462784, 0.5, 1.0 ], 343 | "color2" : [ 0.19771, 0.188048, 0.201856, 1.0 ], 344 | "angle" : 270.0, 345 | "proportion" : 0.39, 346 | "autogradient" : 0 347 | } 348 | , 349 | "selectioncolor" : [ 0.784314, 0.145098, 0.023529, 1.0 ] 350 | } 351 | , 352 | "parentstyle" : "", 353 | "multi" : 0 354 | } 355 | , { 356 | "name" : "cassiel.comment", 357 | "default" : { 358 | "textcolor" : [ 0.65098, 0.666667, 0.662745, 1.0 ] 359 | } 360 | , 361 | "parentstyle" : "", 362 | "multi" : 0 363 | } 364 | , { 365 | "name" : "foo-style", 366 | "default" : { 367 | "fontname" : [ "Courier" ], 368 | "color" : [ 0.720698, 0.16723, 0.080014, 1.0 ], 369 | "elementcolor" : [ 0.836576, 0.903148, 0.643029, 1.0 ] 370 | } 371 | , 372 | "parentstyle" : "", 373 | "multi" : 0 374 | } 375 | , { 376 | "name" : "sky-blue", 377 | "default" : { 378 | "fontname" : [ "Courier" ], 379 | "bgcolor" : [ 0.670588, 0.74902, 0.807843, 1.0 ], 380 | "textcolor_inverse" : [ 0.239216, 0.254902, 0.278431, 1.0 ], 381 | "color" : [ 0.095481, 0.100396, 0.100293, 1.0 ], 382 | "elementcolor" : [ 0.792189, 0.848618, 0.854853, 1.0 ], 383 | "selectioncolor" : [ 0.870588, 0.415686, 0.062745, 1.0 ] 384 | } 385 | , 386 | "parentstyle" : "default", 387 | "multi" : 0 388 | } 389 | , { 390 | "name" : "tap", 391 | "default" : { 392 | "fontname" : [ "Lato Light" ] 393 | } 394 | , 395 | "parentstyle" : "", 396 | "multi" : 0 397 | } 398 | ], 399 | "default_bgcolor" : [ 0.095481, 0.100396, 0.100293, 1.0 ], 400 | "color" : [ 1.0, 1.0, 1.0, 1.0 ], 401 | "elementcolor" : [ 0.32549, 0.345098, 0.372549, 1.0 ], 402 | "selectioncolor" : [ 0.784314, 0.145098, 0.023529, 1.0 ], 403 | "patchlinecolor" : [ 0.960784, 0.827451, 0.156863, 0.9 ], 404 | "bgfillcolor_type" : "gradient", 405 | "bgfillcolor_color1" : [ 0.486435, 0.462784, 0.5, 1.0 ], 406 | "bgfillcolor_color2" : [ 0.19771, 0.188048, 0.201856, 1.0 ], 407 | "bgfillcolor_color" : [ 0.290196, 0.309804, 0.301961, 1.0 ], 408 | "bgfillcolor_angle" : 270.0, 409 | "bgfillcolor_proportion" : 0.39 410 | } 411 | 412 | } 413 | -------------------------------------------------------------------------------- /patchers/lib/CheckConsoleHasError.maxpat: -------------------------------------------------------------------------------- 1 | { 2 | "patcher" : { 3 | "fileversion" : 1, 4 | "appversion" : { 5 | "major" : 7, 6 | "minor" : 2, 7 | "revision" : 5, 8 | "architecture" : "x64", 9 | "modernui" : 1 10 | } 11 | , 12 | "rect" : [ 74.0, 105.0, 638.0, 507.0 ], 13 | "bgcolor" : [ 0.239216, 0.254902, 0.278431, 1.0 ], 14 | "editing_bgcolor" : [ 0.32549, 0.345098, 0.372549, 1.0 ], 15 | "bglocked" : 0, 16 | "openinpresentation" : 0, 17 | "default_fontsize" : 12.0, 18 | "default_fontface" : 0, 19 | "default_fontname" : "Courier", 20 | "gridonopen" : 2, 21 | "gridsize" : [ 15.0, 5.0 ], 22 | "gridsnaponopen" : 2, 23 | "objectsnaponopen" : 0, 24 | "statusbarvisible" : 2, 25 | "toolbarvisible" : 1, 26 | "lefttoolbarpinned" : 0, 27 | "toptoolbarpinned" : 0, 28 | "righttoolbarpinned" : 0, 29 | "bottomtoolbarpinned" : 0, 30 | "toolbars_unpinned_last_save" : 0, 31 | "tallnewobj" : 0, 32 | "boxanimatetime" : 200, 33 | "enablehscroll" : 1, 34 | "enablevscroll" : 1, 35 | "devicewidth" : 0.0, 36 | "description" : "", 37 | "digest" : "", 38 | "tags" : "", 39 | "style" : "cassiel", 40 | "subpatcher_template" : "cassiel", 41 | "boxes" : [ { 42 | "box" : { 43 | "comment" : "bang when checked", 44 | "id" : "obj-1", 45 | "maxclass" : "outlet", 46 | "numinlets" : 1, 47 | "numoutlets" : 0, 48 | "patching_rect" : [ 270.0, 440.0, 30.0, 30.0 ], 49 | "presentation_rect" : [ 115.0, 426.0, 0.0, 0.0 ], 50 | "style" : "" 51 | } 52 | 53 | } 54 | , { 55 | "box" : { 56 | "id" : "obj-25", 57 | "maxclass" : "message", 58 | "numinlets" : 2, 59 | "numoutlets" : 1, 60 | "outlettype" : [ "" ], 61 | "patching_rect" : [ 240.0, 285.0, 47.0, 20.0 ], 62 | "style" : "", 63 | "text" : "set 1" 64 | } 65 | 66 | } 67 | , { 68 | "box" : { 69 | "id" : "obj-23", 70 | "maxclass" : "button", 71 | "numinlets" : 1, 72 | "numoutlets" : 1, 73 | "outlettype" : [ "bang" ], 74 | "patching_rect" : [ 240.0, 230.0, 24.0, 24.0 ], 75 | "style" : "" 76 | } 77 | 78 | } 79 | , { 80 | "box" : { 81 | "id" : "obj-21", 82 | "maxclass" : "newobj", 83 | "numinlets" : 1, 84 | "numoutlets" : 1, 85 | "outlettype" : [ "" ], 86 | "patching_rect" : [ 180.0, 35.0, 111.0, 20.0 ], 87 | "style" : "", 88 | "text" : "loadmess set 0" 89 | } 90 | 91 | } 92 | , { 93 | "box" : { 94 | "color" : [ 0.7, 0.4, 0.3, 1.0 ], 95 | "id" : "obj-20", 96 | "maxclass" : "newobj", 97 | "numinlets" : 1, 98 | "numoutlets" : 0, 99 | "patching_rect" : [ 300.0, 280.0, 297.0, 20.0 ], 100 | "style" : "", 101 | "text" : "test.log #1:ExpectedMatch" 102 | } 103 | 104 | } 105 | , { 106 | "box" : { 107 | "id" : "obj-17", 108 | "maxclass" : "newobj", 109 | "numinlets" : 1, 110 | "numoutlets" : 5, 111 | "outlettype" : [ "", "", "", "", "" ], 112 | "patching_rect" : [ 240.0, 175.0, 103.0, 20.0 ], 113 | "style" : "", 114 | "text" : "regexp #2" 115 | } 116 | 117 | } 118 | , { 119 | "box" : { 120 | "id" : "obj-16", 121 | "linecount" : 2, 122 | "maxclass" : "comment", 123 | "numinlets" : 1, 124 | "numoutlets" : 0, 125 | "patching_rect" : [ 315.0, 40.0, 150.0, 30.0 ], 126 | "style" : "cassiel.comment", 127 | "text" : "#1 = test name; #2 = regexp to match" 128 | } 129 | 130 | } 131 | , { 132 | "box" : { 133 | "comment" : "bang when checked", 134 | "id" : "obj-14", 135 | "maxclass" : "outlet", 136 | "numinlets" : 1, 137 | "numoutlets" : 0, 138 | "patching_rect" : [ 30.0, 325.0, 30.0, 30.0 ], 139 | "style" : "" 140 | } 141 | 142 | } 143 | , { 144 | "box" : { 145 | "id" : "obj-9", 146 | "maxclass" : "newobj", 147 | "numinlets" : 2, 148 | "numoutlets" : 1, 149 | "outlettype" : [ "" ], 150 | "patching_rect" : [ 105.0, 135.0, 39.0, 20.0 ], 151 | "style" : "", 152 | "text" : "qlim" 153 | } 154 | 155 | } 156 | , { 157 | "box" : { 158 | "id" : "obj-6", 159 | "maxclass" : "button", 160 | "numinlets" : 1, 161 | "numoutlets" : 1, 162 | "outlettype" : [ "bang" ], 163 | "patching_rect" : [ 105.0, 80.0, 24.0, 24.0 ], 164 | "style" : "" 165 | } 166 | 167 | } 168 | , { 169 | "box" : { 170 | "comment" : "bang: check for expected errors", 171 | "id" : "obj-2", 172 | "maxclass" : "inlet", 173 | "numinlets" : 0, 174 | "numoutlets" : 1, 175 | "outlettype" : [ "" ], 176 | "patching_rect" : [ 105.0, 30.0, 30.0, 30.0 ], 177 | "style" : "" 178 | } 179 | 180 | } 181 | , { 182 | "box" : { 183 | "color" : [ 0.7, 0.4, 0.3, 1.0 ], 184 | "id" : "obj-11", 185 | "maxclass" : "newobj", 186 | "numinlets" : 1, 187 | "numoutlets" : 0, 188 | "patching_rect" : [ 180.0, 405.0, 370.0, 20.0 ], 189 | "style" : "", 190 | "text" : "test.assert #1:ExpectedMatch #2" 191 | } 192 | 193 | } 194 | , { 195 | "box" : { 196 | "id" : "obj-10", 197 | "maxclass" : "number", 198 | "numinlets" : 1, 199 | "numoutlets" : 2, 200 | "outlettype" : [ "", "bang" ], 201 | "parameter_enable" : 0, 202 | "patching_rect" : [ 180.0, 355.0, 50.0, 20.0 ], 203 | "style" : "" 204 | } 205 | 206 | } 207 | , { 208 | "box" : { 209 | "id" : "obj-3", 210 | "maxclass" : "newobj", 211 | "numinlets" : 1, 212 | "numoutlets" : 1, 213 | "outlettype" : [ "" ], 214 | "patching_rect" : [ 255.0, 130.0, 60.0, 20.0 ], 215 | "style" : "", 216 | "text" : "error 1" 217 | } 218 | 219 | } 220 | ], 221 | "lines" : [ { 222 | "patchline" : { 223 | "destination" : [ "obj-1", 0 ], 224 | "disabled" : 0, 225 | "hidden" : 0, 226 | "source" : [ "obj-10", 0 ] 227 | } 228 | 229 | } 230 | , { 231 | "patchline" : { 232 | "destination" : [ "obj-11", 0 ], 233 | "disabled" : 0, 234 | "hidden" : 0, 235 | "source" : [ "obj-10", 0 ] 236 | } 237 | 238 | } 239 | , { 240 | "patchline" : { 241 | "destination" : [ "obj-20", 0 ], 242 | "disabled" : 0, 243 | "hidden" : 0, 244 | "source" : [ "obj-17", 2 ] 245 | } 246 | 247 | } 248 | , { 249 | "patchline" : { 250 | "destination" : [ "obj-23", 0 ], 251 | "disabled" : 0, 252 | "hidden" : 0, 253 | "source" : [ "obj-17", 2 ] 254 | } 255 | 256 | } 257 | , { 258 | "patchline" : { 259 | "destination" : [ "obj-6", 0 ], 260 | "disabled" : 0, 261 | "hidden" : 0, 262 | "source" : [ "obj-2", 0 ] 263 | } 264 | 265 | } 266 | , { 267 | "patchline" : { 268 | "destination" : [ "obj-10", 0 ], 269 | "disabled" : 0, 270 | "hidden" : 0, 271 | "source" : [ "obj-21", 0 ] 272 | } 273 | 274 | } 275 | , { 276 | "patchline" : { 277 | "destination" : [ "obj-25", 0 ], 278 | "disabled" : 0, 279 | "hidden" : 0, 280 | "source" : [ "obj-23", 0 ] 281 | } 282 | 283 | } 284 | , { 285 | "patchline" : { 286 | "destination" : [ "obj-10", 0 ], 287 | "disabled" : 0, 288 | "hidden" : 0, 289 | "source" : [ "obj-25", 0 ] 290 | } 291 | 292 | } 293 | , { 294 | "patchline" : { 295 | "destination" : [ "obj-17", 0 ], 296 | "disabled" : 0, 297 | "hidden" : 0, 298 | "source" : [ "obj-3", 0 ] 299 | } 300 | 301 | } 302 | , { 303 | "patchline" : { 304 | "destination" : [ "obj-9", 0 ], 305 | "disabled" : 0, 306 | "hidden" : 0, 307 | "source" : [ "obj-6", 0 ] 308 | } 309 | 310 | } 311 | , { 312 | "patchline" : { 313 | "destination" : [ "obj-10", 0 ], 314 | "disabled" : 0, 315 | "hidden" : 0, 316 | "source" : [ "obj-9", 0 ] 317 | } 318 | 319 | } 320 | , { 321 | "patchline" : { 322 | "destination" : [ "obj-14", 0 ], 323 | "disabled" : 0, 324 | "hidden" : 0, 325 | "source" : [ "obj-9", 0 ] 326 | } 327 | 328 | } 329 | ], 330 | "dependency_cache" : [ ], 331 | "autosave" : 0, 332 | "styles" : [ { 333 | "name" : "cassiel", 334 | "default" : { 335 | "fontname" : [ "Courier" ], 336 | "bgcolor" : [ 0.095481, 0.100396, 0.100293, 1.0 ], 337 | "patchlinecolor" : [ 0.960784, 0.827451, 0.156863, 0.9 ], 338 | "color" : [ 1.0, 1.0, 1.0, 1.0 ], 339 | "elementcolor" : [ 0.32549, 0.345098, 0.372549, 1.0 ], 340 | "bgfillcolor" : { 341 | "type" : "gradient", 342 | "color" : [ 0.290196, 0.309804, 0.301961, 1.0 ], 343 | "color1" : [ 0.486435, 0.462784, 0.5, 1.0 ], 344 | "color2" : [ 0.19771, 0.188048, 0.201856, 1.0 ], 345 | "angle" : 270.0, 346 | "proportion" : 0.39, 347 | "autogradient" : 0 348 | } 349 | , 350 | "selectioncolor" : [ 0.784314, 0.145098, 0.023529, 1.0 ] 351 | } 352 | , 353 | "parentstyle" : "", 354 | "multi" : 0 355 | } 356 | , { 357 | "name" : "cassiel.comment", 358 | "default" : { 359 | "textcolor" : [ 0.65098, 0.666667, 0.662745, 1.0 ] 360 | } 361 | , 362 | "parentstyle" : "", 363 | "multi" : 0 364 | } 365 | , { 366 | "name" : "foo-style", 367 | "default" : { 368 | "fontname" : [ "Courier" ], 369 | "color" : [ 0.720698, 0.16723, 0.080014, 1.0 ], 370 | "elementcolor" : [ 0.836576, 0.903148, 0.643029, 1.0 ] 371 | } 372 | , 373 | "parentstyle" : "", 374 | "multi" : 0 375 | } 376 | , { 377 | "name" : "sky-blue", 378 | "default" : { 379 | "fontname" : [ "Courier" ], 380 | "bgcolor" : [ 0.670588, 0.74902, 0.807843, 1.0 ], 381 | "textcolor_inverse" : [ 0.239216, 0.254902, 0.278431, 1.0 ], 382 | "color" : [ 0.095481, 0.100396, 0.100293, 1.0 ], 383 | "elementcolor" : [ 0.792189, 0.848618, 0.854853, 1.0 ], 384 | "selectioncolor" : [ 0.870588, 0.415686, 0.062745, 1.0 ] 385 | } 386 | , 387 | "parentstyle" : "default", 388 | "multi" : 0 389 | } 390 | , { 391 | "name" : "tap", 392 | "default" : { 393 | "fontname" : [ "Lato Light" ] 394 | } 395 | , 396 | "parentstyle" : "", 397 | "multi" : 0 398 | } 399 | ], 400 | "default_bgcolor" : [ 0.095481, 0.100396, 0.100293, 1.0 ], 401 | "color" : [ 1.0, 1.0, 1.0, 1.0 ], 402 | "elementcolor" : [ 0.32549, 0.345098, 0.372549, 1.0 ], 403 | "selectioncolor" : [ 0.784314, 0.145098, 0.023529, 1.0 ], 404 | "patchlinecolor" : [ 0.960784, 0.827451, 0.156863, 0.9 ], 405 | "bgfillcolor_type" : "gradient", 406 | "bgfillcolor_color1" : [ 0.486435, 0.462784, 0.5, 1.0 ], 407 | "bgfillcolor_color2" : [ 0.19771, 0.188048, 0.201856, 1.0 ], 408 | "bgfillcolor_color" : [ 0.290196, 0.309804, 0.301961, 1.0 ], 409 | "bgfillcolor_angle" : 270.0, 410 | "bgfillcolor_proportion" : 0.39 411 | } 412 | 413 | } 414 | -------------------------------------------------------------------------------- /ruby/rosc/AUTHORS: -------------------------------------------------------------------------------- 1 | This library is based on the original Ruby osc library by Tadayoshi Funaba 2 | which can be found at http://www.funaba.org/en/ruby.html#osc 3 | 4 | Hans Fugal heavily refactored, rewrote, and to some extent 5 | redesigned that code to produce what you have here. 6 | 7 | Thanks to Paul Battley for the ingenious Tiger pack hack. 8 | 9 | Thanks to Von and Jacob Fugal for various very useful contributions. 10 | 11 | Thanks to Liam Staskawicz for some testing and bug-finding. 12 | 13 | Thanks to Jan Krutisch for some debugging and better unit tests. 14 | -------------------------------------------------------------------------------- /ruby/rosc/ChangeLog: -------------------------------------------------------------------------------- 1 | 0.1.3 2 | - Bugfix release, project moving to github (http://github.com/fugalh/rosc) 3 | 0.1.2 4 | - Fixed some bugs, thanks to Liam 5 | 0.1.1 6 | - bugfix release 7 | 0.1 8 | - initial release 9 | -------------------------------------------------------------------------------- /ruby/rosc/LICENSE: -------------------------------------------------------------------------------- 1 | Ruby is copyrighted free software by Yukihiro Matsumoto . 2 | You can redistribute it and/or modify it under either the terms of the GPL 3 | (see GPL.txt file), or the conditions below: 4 | 5 | 1. You may make and give away verbatim copies of the source form of the 6 | software without restriction, provided that you duplicate all of the 7 | original copyright notices and associated disclaimers. 8 | 9 | 2. You may modify your copy of the software in any way, provided that 10 | you do at least ONE of the following: 11 | 12 | a) place your modifications in the Public Domain or otherwise 13 | make them Freely Available, such as by posting said 14 | modifications to Usenet or an equivalent medium, or by allowing 15 | the author to include your modifications in the software. 16 | 17 | b) use the modified software only within your corporation or 18 | organization. 19 | 20 | c) rename any non-standard executables so the names do not conflict 21 | with standard executables, which must also be provided. 22 | 23 | d) make other distribution arrangements with the author. 24 | 25 | 3. You may distribute the software in object code or executable 26 | form, provided that you do at least ONE of the following: 27 | 28 | a) distribute the executables and library files of the software, 29 | together with instructions (in the manual page or equivalent) 30 | on where to get the original distribution. 31 | 32 | b) accompany the distribution with the machine-readable source of 33 | the software. 34 | 35 | c) give non-standard executables non-standard names, with 36 | instructions on where to get the original software distribution. 37 | 38 | d) make other distribution arrangements with the author. 39 | 40 | 4. You may modify and include the part of the software into any other 41 | software (possibly commercial). But some files in the distribution 42 | are not written by the author, so that they are not under this terms. 43 | 44 | They are gc.c(partly), utils.c(partly), regex.[ch], st.[ch] and some 45 | files under the ./missing directory. See each file for the copying 46 | condition. 47 | 48 | 5. The scripts and library files supplied as input to or produced as 49 | output from the software do not automatically fall under the 50 | copyright of the software, but belong to whomever generated them, 51 | and may be sold commercially, and may be aggregated with this 52 | software. 53 | 54 | 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR 55 | IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED 56 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 57 | PURPOSE. 58 | -------------------------------------------------------------------------------- /ruby/rosc/README: -------------------------------------------------------------------------------- 1 | = rosc - OpenSound Control for Ruby 2 | == Synopsis 3 | 4 | require 'osc' 5 | 6 | Host = 'localhost' 7 | Port = 5000 8 | 9 | s = OSC::UDPServer.new 10 | s.bind Host, Port 11 | 12 | c = OSC::UDPSocket.new 13 | m = OSC::Message.new('/foo', 'fi', Math::PI, 42) 14 | c.send m, 0, Host, Port 15 | 16 | s.add_method '/f*', 'fi' do |msg| 17 | domain, port, host, ip = msg.source 18 | puts "#{msg.address} -> #{msg.args.inspect} from #{host}:#{port}" 19 | end 20 | Thread.new do 21 | s.serve 22 | end 23 | sleep 5 24 | 25 | #=> /foo -> [3.14159274101257, 42] from localhost:50843 26 | 27 | == Requirements 28 | - Ruby 29 | 30 | == Installation 31 | 32 | sudo ruby setup.rb 33 | 34 | == Details 35 | See the OSC home page[1], especially the "State of the Art" paper (for an 36 | overview) and the specification. This library makes OSC easy, but you will 37 | still need to understand OSC concepts and limitations. 38 | 39 | The important classes are Message, Bundle, UDPSocket, and UDPServer. If you 40 | want to make your own server on a different transport (e.g. TCP or UNIX 41 | sockets, which are still on the TODO list), you will want to use the Server 42 | mixin. 43 | 44 | Please read the AUTHORS file for credits and see the TODO list for planned 45 | enhancements. 46 | 47 | 1. http://www.cnmat.berkeley.edu/OpenSoundControl 48 | 49 | == Download/Contribute 50 | The project is hosted at GitHub: http://github.com/fugalh/rosc 51 | 52 | == Examples 53 | Send me your interesting examples and I'll include them. 54 | 55 | == License 56 | Copyright (C) 2007 Hans Fugal and Tadayoshi Funaba 57 | 58 | Distributed under Ruby's license. See the LICENSE file. 59 | -------------------------------------------------------------------------------- /ruby/rosc/Rakefile: -------------------------------------------------------------------------------- 1 | require 'rake/testtask' 2 | require 'rake/rdoctask' 3 | 4 | task :default => :rdoc 5 | Rake::RDocTask.new do |rd| 6 | rd.rdoc_files.add ['README','AUTHORS','TODO','lib/**/*.rb'] 7 | #rd.template = ENV['HOME']+'/src/allison/allison.rb' 8 | rd.rdoc_dir = 'doc' 9 | rd.options = ['-x_darcs','-xtest'] 10 | rd.title = 'rosc' 11 | rd.options += ['--line-numbers','--inline-source'] 12 | end 13 | 14 | Rake::TestTask.new do |t| 15 | #t.verbose = true 16 | end 17 | 18 | # vim: filetype=ruby 19 | -------------------------------------------------------------------------------- /ruby/rosc/TODO: -------------------------------------------------------------------------------- 1 | - nonstandard types 2 | - TCP and UNIX sockets 3 | - OSC urls 4 | -------------------------------------------------------------------------------- /ruby/rosc/examples/readme.rb: -------------------------------------------------------------------------------- 1 | require 'osc' 2 | Host = 'localhost' 3 | Port = 5000 4 | 5 | s = OSC::UDPServer.new 6 | s.bind Host, Port 7 | 8 | c = OSC::UDPSocket.new 9 | m = OSC::Message.new('/foo', 'fi', Math::PI, 42) 10 | c.send m, 0, Host, Port 11 | 12 | s.add_method '/f*', 'fi' do |msg| 13 | domain, port, host, ip = msg.source 14 | puts "#{msg.address} -> #{msg.args.inspect} from #{host}:#{port}" 15 | end 16 | Thread.new do 17 | s.serve 18 | end 19 | sleep 5 20 | -------------------------------------------------------------------------------- /ruby/rosc/lib/osc.rb: -------------------------------------------------------------------------------- 1 | require 'time' 2 | require 'forwardable' 3 | require 'stringio' 4 | require 'yaml' 5 | 6 | # Test for broken pack/unpack 7 | if [1].pack('n') == "\001\000" 8 | class String 9 | alias_method :broken_unpack, :unpack 10 | def unpack(spec) 11 | broken_unpack(spec.tr("nNvV","vVnN")) 12 | end 13 | end 14 | class Array 15 | alias_method :broken_pack, :pack 16 | def pack(spec) 17 | broken_pack(spec.tr("nNvV","vVnN")) 18 | end 19 | end 20 | end 21 | 22 | 23 | class StringIO 24 | def skip(n) 25 | self.seek(n, IO::SEEK_CUR) 26 | end 27 | def skip_padding 28 | self.skip((4-pos)%4) 29 | end 30 | end 31 | 32 | # Of particular interest are OSC::Client, OSC::Server, OSC::Message and 33 | # OSC::Bundle. 34 | module OSC 35 | # 64-bit big-endian fixed-point time tag 36 | class TimeTag 37 | JAN_1970 = 0x83aa7e80 38 | # nil:: immediately 39 | # Numeric:: seconds since January 1, 1900 00:00 40 | # Numeric,Numeric:: int,frac parts of a TimeTag. 41 | # Time:: convert from Time object 42 | def initialize(*args) 43 | t = args 44 | t = t.first if t and t.size == 1 45 | case t 46 | when NIL # immediately 47 | @int = 0 48 | @frac = 1 49 | when Numeric 50 | @int, fr = t.divmod(1) 51 | @frac = (fr * (2**32)).to_i 52 | when Array 53 | @int,@frac = t 54 | when Time 55 | @int, fr = (t.to_f+JAN_1970).divmod(1) 56 | @frac = (fr * (2**32)).to_i 57 | else 58 | raise ArgumentError 59 | end 60 | end 61 | attr_accessor :int, :frac 62 | def to_i; to_f.to_i; end 63 | # Ruby's Float can handle the 64 bits so we have the luxury of dealing with 64 | # Float directly 65 | def to_f; @int.to_f + @frac.to_f/(2**32); end 66 | # [int,frac] 67 | def to_a; [@int,@frac]; end 68 | # Human-readable, like the output of Time#to_s 69 | def to_s; to_time.to_s; end 70 | # Ruby Time object 71 | def to_time; Time.at(to_f-JAN_1970); end 72 | alias :time :to_time 73 | def self.now; TimeTag.new(Time.now); end 74 | def method_missing(sym, *args) 75 | time.__send__(sym, *args) 76 | end 77 | def to_yaml 78 | to_a.to_yaml 79 | end 80 | end 81 | 82 | class Blob < String 83 | end 84 | 85 | class Message 86 | attr_accessor :address, :args 87 | # The source of this message, usually something like ["AF_INET", 50475, 88 | # 'localhost','127.0.0.1'] 89 | attr_accessor :source 90 | 91 | # address:: The OSC address (a String) 92 | # types:: The OSC type tags string 93 | # args:: arguments. must match type tags in arity 94 | # 95 | # Example: 96 | # Message.new('/foo','ff', Math::PI, Math::E) 97 | # 98 | # Arguments will be coerced as indicated by the type tags. If types is nil, 99 | # type tags will be inferred from arguments. 100 | def initialize(address, types=nil, *args) 101 | if types and types.size != args.size 102 | raise ArgumentError, 'type/args arity mismatch' 103 | end 104 | 105 | @address = address 106 | @args = [] 107 | 108 | if types 109 | args.each_with_index do |arg, i| 110 | case types[i] 111 | when ?i; @args << arg.to_i 112 | when ?f; @args << arg.to_f 113 | when ?s; @args << arg.to_s 114 | when ?b; @args << Blob.new(arg) 115 | else 116 | raise ArgumentError, "unknown type tag '#{@types[i].inspect}'" 117 | end 118 | end 119 | else 120 | args.each do |arg| 121 | case arg 122 | when Fixnum,Float,String,TimeTag,Blob 123 | @args << arg 124 | else 125 | raise ArgumentError, "Object has unknown OSC type: '#{arg}'" 126 | end 127 | end 128 | end 129 | end 130 | 131 | def types 132 | @args.collect {|a| Packet.tag a}.join 133 | end 134 | alias :typetag :types 135 | 136 | # Encode this message for transport 137 | def encode 138 | Packet.encode(self) 139 | end 140 | # string representation. *not* the raw representation, for that use 141 | # encode. 142 | def to_s 143 | "#{address},#{types},#{args.collect{|a| a.to_s}.join(',')}" 144 | end 145 | def to_yaml 146 | {'address'=>address, 'types'=>types, 'args'=>args}.to_yaml 147 | end 148 | 149 | extend Forwardable 150 | include Enumerable 151 | 152 | de = (Array.instance_methods - self.instance_methods) 153 | de -= %w(assoc flatten flatten! pack rassoc transpose) 154 | de += %w(include? sort) 155 | 156 | def_delegators(:@args, *de) 157 | 158 | undef_method :zip 159 | end 160 | 161 | # bundle of messages and/or bundles 162 | class Bundle 163 | attr_accessor :timetag 164 | attr_accessor :args 165 | attr_accessor :source 166 | alias :timestamp :timetag 167 | alias :messages :args 168 | alias :contents :args 169 | alias :to_a :args 170 | 171 | # New bundle with time and messages 172 | def initialize(t=nil, *args) 173 | @timetag = 174 | case t 175 | when TimeTag 176 | t 177 | else 178 | TimeTag.new(t) 179 | end 180 | @args = args 181 | end 182 | 183 | def to_yaml 184 | {'timestamp'=>timetag, 'contents'=>contents}.to_yaml 185 | end 186 | 187 | extend Forwardable 188 | include Enumerable 189 | 190 | de = (Array.instance_methods - self.instance_methods) 191 | de -= %w(assoc flatten flatten! pack rassoc transpose) 192 | de += %w(include? sort) 193 | 194 | def_delegators(:@args, *de) 195 | 196 | undef_method :zip 197 | 198 | def encode 199 | Packet.encode(self) 200 | end 201 | 202 | end 203 | 204 | # Unit of transmission. Really needs revamping 205 | module Packet 206 | # XXX I might fold this and its siblings back into the decode case 207 | # statement 208 | def self.decode_int32(io) 209 | i = io.read(4).unpack('N')[0] 210 | i = 2**32 - i if i > (2**31-1) # two's complement 211 | i 212 | end 213 | 214 | def self.decode_float32(io) 215 | f = io.read(4).unpack('g')[0] 216 | f 217 | end 218 | 219 | def self.decode_string(io) 220 | s = io.gets("\0").chomp("\0") 221 | io.skip_padding 222 | s 223 | end 224 | 225 | def self.decode_blob(io) 226 | l = io.read(4).unpack('N')[0] 227 | b = io.read(l) 228 | io.skip_padding 229 | b 230 | end 231 | 232 | def self.decode_timetag(io) 233 | t1 = io.read(4).unpack('N')[0] 234 | t2 = io.read(4).unpack('N')[0] 235 | TimeTag.new [t1,t2] 236 | end 237 | 238 | # Takes a string containing one packet 239 | def self.decode(packet) 240 | # XXX I think it would have been better to use a StringScanner. Maybe I 241 | # will convert it someday... 242 | if (packet == nil) 243 | return Message.new("/") 244 | end 245 | io = StringIO.new(packet) 246 | id = decode_string(io) 247 | if id == '#bundle' 248 | b = Bundle.new(decode_timetag(io)) 249 | until io.eof? 250 | l = io.read(4).unpack('N')[0] 251 | s = io.read(l) 252 | b << decode(s) 253 | end 254 | b 255 | elsif id =~ /^\// 256 | m = Message.new(id) 257 | if io.getc == ?, 258 | tags = decode_string(io) 259 | tags.scan(/./) do |t| 260 | case t 261 | when 'i' 262 | m << decode_int32(io) 263 | when 'f' 264 | m << decode_float32(io) 265 | when 's' 266 | m << decode_string(io) 267 | when 'b' 268 | m << decode_blob(io) 269 | 270 | # right now we skip over nonstandard datatypes, but we'll want to 271 | # add these datatypes too. 272 | when /[htd]/; io.read(8) 273 | when 'S'; decode_string(io) 274 | when /[crm]/; io.read(4) 275 | when /[TFNI\[\]]/; 276 | end 277 | end 278 | end 279 | m 280 | end 281 | end 282 | 283 | def self.pad(s) 284 | s + ("\000" * ((4 - s.size)%4)) 285 | end 286 | 287 | def self.tag(o) 288 | case o 289 | when Fixnum; 'i' 290 | when TimeTag; 't' 291 | when Float; 'f' 292 | when Blob; 'b' 293 | when String; 's' 294 | else; nil 295 | end 296 | end 297 | 298 | def self.encode(o) 299 | case o 300 | when Fixnum; [o].pack 'N' 301 | when Float; [o].pack 'g' 302 | when Blob; pad([o.size].pack('N') + o) 303 | when String; pad(o.sub(/\000.*\Z/, '') + "\000") 304 | when TimeTag; o.to_a.pack('NN') 305 | 306 | when Message 307 | s = encode(o.address) 308 | s << encode(','+o.types) 309 | s << o.args.collect{|x| encode(x)}.join 310 | 311 | when Bundle 312 | s = encode('#bundle') 313 | s << encode(o.timetag) 314 | s << o.args.collect { |x| 315 | x2 = encode(x); [x2.size].pack('N') + x2 316 | }.join 317 | end 318 | end 319 | 320 | private_class_method :decode_int32, :decode_float32, :decode_string, 321 | :decode_blob, :decode_timetag 322 | end 323 | end 324 | 325 | 326 | libdir = Dir.getwd + "/rosc/lib" 327 | olddir = Dir.getwd 328 | Dir.chdir libdir # change to libdir so that requires work 329 | require "#{libdir}/osc/pattern" 330 | require "#{libdir}/osc/server" 331 | require "#{libdir}/osc/udp" 332 | require "#{libdir}/osc/udp_server_with_count" 333 | Dir.chdir olddir 334 | 335 | -------------------------------------------------------------------------------- /ruby/rosc/lib/osc/pattern.rb: -------------------------------------------------------------------------------- 1 | require 'set' 2 | module OSC 3 | class Pattern < String 4 | # Create an OSC pattern from a string or (experimental) from a Regex. 5 | def initialize(s) 6 | case s 7 | when Regexp # This is experimental 8 | s = Regexp.source s 9 | s.gsub! /(\\\\)*\[^\/\]\*/, "\1*" 10 | s.gsub! /(\\\\)*\[^\/\]/, "\1?" 11 | s.gsub! /(\\\\)*\[^/, "\1[!" 12 | s.gsub! /(\\\\)*\(/, "\1{" 13 | s.gsub! /(\\\\)*\|/, "\1," 14 | s.gsub! /(\\\\)*\)/, "\1}" 15 | s.gsub! /\\\\/, "\\" 16 | end 17 | super s 18 | end 19 | 20 | # Return a Regex representing this pattern 21 | def regexp 22 | s = Regexp.escape self 23 | s.gsub! /\\\?/, '[^/]' 24 | s.gsub! /\\\*/, '[^/]*' 25 | s.gsub! /\\\[!/, '[^' 26 | s.gsub! /\\\]/, ']' 27 | s.gsub! /\\\{/, '(' 28 | s.gsub! /,/, '|' 29 | s.gsub! /\\\}/, ')' 30 | Regexp.new s 31 | end 32 | 33 | # Do these two patterns intersect? 34 | #-- 35 | # This might be improved by following the (much simpler, but related) 36 | # algorithm here: 37 | # 38 | # http://groups.google.com/group/comp.theory/browse_frm/thread/f33e033269bd5ab0/c87e19081f45454c?lnk=st&q=regular+expression+intersection&rnum=1&hl=en#c87e19081f45454c 39 | # 40 | # That is, convert each regexp into an NFA, then generate the set of valid 41 | # state pairs, then check if the pair of final states is included. 42 | # That's basically what I'm doing here, but I'm not generating all the 43 | # state pairs, I'm just doing a search. My way may be faster and/or 44 | # smaller, or it may not. My initial feeling is that it is faster since 45 | # we're basically doing a depth-first search and OSC patterns are going to 46 | # tend to be fairly simple. Still it might be a fun experiment for the 47 | # masochistic. 48 | def self.intersect?(s1,s2) 49 | r = /\*|\?|\[[^\]]*\]|\{[^\}]*\}|./ 50 | a = s1.to_s.scan r 51 | b = s2.to_s.scan r 52 | q = [[a,b]] 53 | until q.empty? 54 | q.uniq! 55 | a,b = q.pop 56 | a = a.dup 57 | b = b.dup 58 | 59 | return true if a.empty? and b.empty? 60 | next if a.empty? or b.empty? 61 | 62 | x,y = a.shift, b.shift 63 | 64 | # branch {} 65 | if x =~ /^\{/ 66 | x.scan /[^\{\},]+/ do |x| 67 | q.push [x.scan(/./)+a,[y]+b] 68 | end 69 | next 70 | end 71 | if y =~ /^\{/ 72 | y.scan /[^\{\},]+/ do |y| 73 | q.push [[x]+a,y.scan(/./)+b] 74 | end 75 | next 76 | end 77 | 78 | # sort 79 | if y =~ /^\[/ 80 | x,y = y,x 81 | a,b = b,a 82 | end 83 | if y =~ /^(\*|\?)/ 84 | x,y = y,x 85 | a,b = b,a 86 | end 87 | 88 | # match 89 | case x 90 | when '*' 91 | unless y == '/' 92 | q.push [a,b] 93 | q.push [[x]+a,b] 94 | end 95 | if y == '*' 96 | q.push [a,[y]+b] 97 | q.push [[x]+a,b] 98 | end 99 | when '?' 100 | q.push [a,b] unless y == '/' 101 | q.push [a,[y]+b] if y == '*' 102 | when /^\[/ 103 | xinv = (x[1] == ?!) 104 | yinv = (y =~ /^\[!/) 105 | x = x[(xinv ? 2 : 1)..-2].scan(/./).to_set 106 | if y =~ /^\[/ 107 | y = y[(yinv ? 2 : 1)..-2].scan(/./).to_set 108 | else 109 | y = [y].to_set 110 | end 111 | 112 | # simplifying assumption: nobody in their right mind is going to do 113 | # [^everyprintablecharacter] 114 | if xinv and yinv 115 | q.push [a,b] 116 | elsif xinv and not yinv 117 | q.push [a,b] unless (y-x).empty? 118 | elsif not xinv and yinv 119 | q.push [a,b] unless (x-y).empty? 120 | else 121 | q.push [a,b] unless (x&y).empty? 122 | end 123 | else 124 | q.push [a,b] if x == y 125 | end 126 | end 127 | 128 | false # no intersection 129 | end 130 | end 131 | end 132 | -------------------------------------------------------------------------------- /ruby/rosc/lib/osc/server.rb: -------------------------------------------------------------------------------- 1 | module OSC 2 | # Mixin for making servers. 3 | # Your job is to read a packet and call dispatch(Packet.decode(raw)), ad 4 | # infinitum (e.g. in a method named serve). 5 | module Server 6 | # prock.respond_to?(:call) #=> true 7 | # Pass an OSC pattern, a typespec, and either prock or a block. 8 | # The block/prock will be called if the pattern and typspec match. Numeric 9 | # types will be coerced, so e.g. 'fi' would match 'ii' and the float would 10 | # be coerced to an int. 11 | def add_method(pat, typespec, prock=nil, &block) 12 | pat = Pattern.new(pat) unless (Pattern === pat || pat.nil?) 13 | if block_given? and prock 14 | raise ArgumentError, 'Specify either a block or a Proc, not both.' 15 | end 16 | prock = block if block_given? 17 | unless prock.respond_to?(:call) 18 | raise ArgumentError, "Prock doesn't respond to :call" 19 | end 20 | unless typespec.nil? or typespec =~ /[ifsb]*/ 21 | raise ArgumentError, "Bad typespec '#{typespec}'" 22 | end 23 | @cb ||= [] 24 | @cb << [pat, typespec, prock] 25 | end 26 | 27 | # dispatch the provided message. It can be raw or already decoded with 28 | # Packet.decode 29 | def dispatch(mesg) 30 | case mesg 31 | when Bundle, Message 32 | else 33 | mesg = Packet.decode(mesg) 34 | end 35 | 36 | case mesg 37 | when Bundle; dispatch_bundle(mesg) 38 | when Message 39 | unless @cb.nil? 40 | @cb.each do |pat, typespec, obj| 41 | if pat.nil? or Pattern.intersect?(pat, mesg.address) 42 | if typespec 43 | if typespec.size == mesg.args.size 44 | match = true 45 | typespec.size.times do |i| 46 | c = typespec[i] 47 | case c 48 | when ?i, ?f 49 | match &&= (Numeric === mesg.args[i]) 50 | when ?s, ?b 51 | match &&= (String === mesg.args[i]) 52 | end 53 | end 54 | if match 55 | typespec.size.times do |i| 56 | case typespec[i] 57 | when ?i 58 | mesg.args[i] = mesg.args[i].to_i 59 | when ?f 60 | mesg.args[i] = mesg.args[i].to_f 61 | when ?s,?b 62 | mesg.args[i] = mesg.args[i].to_s 63 | mesg.args[i] = mesg.args[i].to_s 64 | end 65 | end 66 | obj.call(mesg) 67 | end 68 | end 69 | else # no typespec 70 | obj.call(mesg) 71 | end 72 | end # pattern match 73 | end # @cb.each 74 | end # unless @cb.nil? 75 | else 76 | raise "bad mesg" 77 | end 78 | end 79 | 80 | # May create a new thread to wait to dispatch according to p.timetag. 81 | def dispatch_bundle(p) 82 | diff = p.timetag.to_f - TimeTag.now.to_f 83 | if diff <= 0 84 | p.each {|m| m.source = p.source; dispatch m} 85 | else 86 | Thread.new do 87 | sleep diff 88 | p.each {|m| m.source = p.source; dispatch m} 89 | end 90 | end 91 | end 92 | end 93 | end 94 | -------------------------------------------------------------------------------- /ruby/rosc/lib/osc/transport.rb: -------------------------------------------------------------------------------- 1 | module OSC 2 | # Mixin for OSC transports. You implement (or in many cases just alias) 3 | # send_raw, recvfrom_raw, and recv_raw, which have the semantics of send, 4 | # recvfrom, and recv in e.g. UDPSocket 5 | module Transport 6 | # Send a Message, Bundle, or even raw data 7 | def send(msg, *args) 8 | case msg 9 | when Message,Bundle 10 | send_raw(msg.encode, *args) 11 | else 12 | send_raw(msg, *args) 13 | end 14 | end 15 | 16 | # Receive a Message, Bundle, or raw data and the sender. The source 17 | # attribute of the Message or Bundle is also set to sender. e.g. 18 | # packet, sender = udp_osc_client.recvfrom(32768) 19 | def recvfrom(*args) 20 | data, sender = recvfrom_raw(*args) 21 | m = Packet.decode(data) 22 | m.source = sender 23 | [m, sender] 24 | rescue 25 | [data, sender] 26 | end 27 | 28 | # Receive a Message, Bundle, or raw data. 29 | def recv(*args) 30 | data = recv_raw(*args) 31 | Packet.decode(data) 32 | rescue 33 | end 34 | 35 | # Send a Message/Bundle with a timestamp (a Time or TimeTag object). 36 | def send_timestamped(msg, ts, *args) 37 | m = Bundle.new(ts, msg) 38 | send(m, *args) 39 | end 40 | alias :send_ts :send_timestamped 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /ruby/rosc/lib/osc/udp.rb: -------------------------------------------------------------------------------- 1 | require "#{Dir.pwd}/osc/transport" 2 | require "socket" 3 | 4 | module OSC 5 | # A ::UDPSocket with a send method that accepts a Message or Bundle or 6 | # a raw String. 7 | class UDPSocket < ::UDPSocket 8 | alias :send_raw :send 9 | alias :recvfrom_raw :recvfrom 10 | alias :recv_raw :recv 11 | include Transport 12 | end 13 | 14 | class UDPServer < OSC::UDPSocket 15 | MAX_MSG_SIZE=32768 16 | include Server 17 | def serve 18 | loop do 19 | p, sender = recvfrom(MAX_MSG_SIZE) 20 | dispatch p 21 | end 22 | end 23 | 24 | # send msg2 as a reply to msg1 25 | def reply(msg1, msg2) 26 | domain, port, host, ip = msg2.source 27 | send(msg2, 0, host, port) 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /ruby/rosc/lib/osc/udp_server_with_count.rb: -------------------------------------------------------------------------------- 1 | module OSC 2 | class UDPServerWithCount < UDPServer 3 | attr_reader :num_messages_received 4 | 5 | def initialize 6 | @num_messages_received = 0 7 | super 8 | end 9 | 10 | def serve 11 | #only loop when we're receiving non-nil values 12 | continue = true 13 | while continue do 14 | p, sender = recvfrom(MAX_MSG_SIZE) 15 | if p 16 | dispatch p 17 | @num_messages_received += 1 18 | else 19 | continue = false 20 | end 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /ruby/rosc/test/test_osc.rb: -------------------------------------------------------------------------------- 1 | $:.unshift(File.dirname(__FILE__) + "/../lib/") 2 | require 'osc' 3 | require 'time' 4 | require 'test/unit' 5 | 6 | class TC_OSC < Test::Unit::TestCase 7 | include OSC 8 | # def setup 9 | # end 10 | 11 | # def teardown 12 | # end 13 | 14 | def test_datatype 15 | s = 'foo' 16 | i = 42 17 | f = 3.14 18 | 19 | assert_equal 'i', Packet.tag(i) 20 | assert_equal 'f', Packet.tag(f) 21 | assert_equal 's', Packet.tag(s) 22 | assert_equal s+"\000", Packet.encode(s) 23 | b = Blob.new("foobardoobar\0\0x200") 24 | assert_equal 'b', Packet.tag(b) 25 | assert_equal b.size+4 + (b.size+4)%4, Packet.encode(b).size 26 | end 27 | 28 | def test_timetag 29 | t1 = TimeTag::JAN_1970 30 | t2 = Time.now 31 | t3 = t2.to_f+t1 32 | 33 | tt = TimeTag.new t2 34 | assert_equal t3, tt.to_f 35 | assert_equal t3.floor, tt.to_i 36 | assert_equal t3.floor - t3, tt.to_i - tt.to_f 37 | assert_equal [0,1].pack('NN'), Packet.encode(TimeTag.new(nil)) 38 | assert_equal t2.to_i,tt.to_time.to_i # to_f has roundoff error at the lsb 39 | end 40 | 41 | def test_message 42 | a = 'foo' 43 | b = 'quux' 44 | m = Message.new '/foobar', 'ssi', a, b, 1 45 | assert_equal "/foobar\000"+",ssi\000\000\000\000"+ 46 | "foo\000"+"quux\000\000\000\000"+"\000\000\000\001", Packet.encode(m) 47 | end 48 | 49 | def test_bundle 50 | m1 = Message.new '/foo','s','foo' 51 | m2 = Message.new '/bar','s','bar' 52 | t = Time.now 53 | b = Bundle.new(TimeTag.new(Time.at(t + 10)), m1, m2) 54 | b2 = Bundle.new(nil, b, m1) 55 | 56 | assert_equal 10, b.timetag.to_time.to_i - t.to_i 57 | e = Packet.encode(b2) 58 | assert_equal '#bundle', e[0,7] 59 | assert_equal "\000\000\000\000\000\000\000\001", e[8,8] 60 | assert_equal '#bundle', e[16+4,7] 61 | assert_equal '/foo', e[16+4+Packet.encode(b).size+4,4] 62 | assert_equal 0, e.size % 4 63 | 64 | assert_instance_of Array, b2.to_a 65 | assert_instance_of Bundle, b2.to_a[0] 66 | assert_instance_of Message, b2.to_a[1] 67 | 68 | bundle = Packet.decode(e) 69 | assert_instance_of Bundle, bundle 70 | 71 | 72 | end 73 | 74 | 75 | 76 | def test_packet 77 | m = Message.new '/foo','s','foo' 78 | b = Bundle.new nil,m 79 | 80 | m2 = Packet.decode("/foo\000\000\000\000,s\000\000foo\000") 81 | assert_equal m.address,m2.address 82 | m2 = Packet.decode(Packet.encode(m)) 83 | assert_equal m.address,m2.address 84 | assert_equal m.typetag,m2.typetag 85 | assert_equal m.args.size,m2.args.size 86 | b2 = Packet.decode(Packet.encode(b)) 87 | assert_equal b.args.size,b2.args.size 88 | end 89 | 90 | class TestServer 91 | include OSC::Transport 92 | include OSC::Server 93 | def test_request(p) 94 | send p 95 | end 96 | 97 | def send_raw(msg, *args) 98 | dispatch msg 99 | end 100 | 101 | end 102 | 103 | def test_server 104 | s = TestServer.new 105 | s.add_method('/foo/bar',nil) { |msg| 106 | assert_equal 'si',msg.typetag 107 | assert_equal 'Hello, World!',msg[0] 108 | assert_equal 42,msg[1] 109 | } 110 | s.test_request Message.new("/foo/bar",'si','Hello, World!',42) 111 | end 112 | 113 | def test_server_with_bundle 114 | s = TestServer.new 115 | s.add_method('/foo/bar',nil) { |msg| 116 | assert_equal 'si',msg.typetag 117 | assert_equal 'Hello, World!',msg[0] 118 | assert_equal 42,msg[1] 119 | } 120 | s.test_request Bundle.new(nil, Message.new("/foo/bar",'si','Hello, World!',42), Message.new("/foo/bar",'si','Hello, World!',42), Message.new("/foo/bar",'si','Hello, World!',42)) 121 | end 122 | 123 | def test_pattern 124 | # test * 125 | assert Pattern.intersect?('/*/bar/baz','/foo/*/baz') 126 | assert Pattern.intersect?('/f*','/*o') 127 | assert ! Pattern.intersect?('/f*','/foo/bar') 128 | assert ! Pattern.intersect?('/f*','/bar') 129 | # test ? 130 | assert Pattern.intersect?('/fo?/bar','/foo/?ar') 131 | assert ! Pattern.intersect?('/foo?','/foo') 132 | # test [] 133 | assert Pattern.intersect?('/foo/ba[rz]','/foo/bar') 134 | assert Pattern.intersect?('/[!abcde]/a','/[!abcde]/a') 135 | assert Pattern.intersect?('/[!abcde]/a','/f/a') 136 | assert Pattern.intersect?('/[!abcde]/a','/[abf]/a') 137 | assert ! Pattern.intersect?('/[ab]/a','/[!abc]/a') 138 | assert ! Pattern.intersect?('/[abcde]','/[!abcde]') 139 | assert ! Pattern.intersect?('/[abcde]','/f') 140 | assert ! Pattern.intersect?('/[!abcde]','/a') 141 | # test {} 142 | assert Pattern.intersect?('/{foo,bar,baz}','/foo') 143 | assert Pattern.intersect?('/{foo,bar,baz}','/bar') 144 | assert Pattern.intersect?('/{foo,bar,baz}','/baz') 145 | assert ! Pattern.intersect?('/{foo,bar,baz}','/quux') 146 | assert ! Pattern.intersect?('/{foo,bar,baz}','/fo') 147 | # * with *,?,[] 148 | assert Pattern.intersect?('/*/bar','/*/ba?') 149 | assert Pattern.intersect?('/*/bar','/*x/ba?') 150 | assert Pattern.intersect?('/*/bar','/?/ba?') 151 | assert Pattern.intersect?('/*/bar','/?x/ba?') 152 | assert Pattern.intersect?('/*/bar','/[abcde]/ba?') 153 | assert Pattern.intersect?('/*/bar','/[abcde]x/ba?') 154 | assert Pattern.intersect?('/*/bar','/[!abcde]/ba?') 155 | assert Pattern.intersect?('/*/bar','/[!abcde]x/ba?') 156 | # ? with [] 157 | assert Pattern.intersect?('/?','/[abcde]') 158 | assert Pattern.intersect?('/?','/[!abcde]') 159 | end 160 | end 161 | -------------------------------------------------------------------------------- /ruby/test.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # Run automated tests in Max 3 | # Copyright (c) 2014, Cycling '74 4 | 5 | puts "Max Automated Test Runner" 6 | 7 | olddir = Dir.getwd 8 | estring = "" 9 | testpass = "" 10 | 11 | require 'rubygems' 12 | require 'timeout' 13 | gemlist = `gem list`.strip 14 | if !/^sqlite3/.match gemlist 15 | puts 16 | puts "-> You are missing the 'sqlite3' rubygem, which you need for automated testing." 17 | puts "-> Please type 'gem install sqlite3' in the Terminal." 18 | puts 19 | exit 20 | end 21 | require 'fileutils' 22 | require 'pathname' 23 | require 'sqlite3' 24 | require "#{olddir}/rosc/lib/osc.rb" 25 | require "open3" 26 | 27 | 28 | ################################################################### 29 | # argument processing 30 | ################################################################### 31 | 32 | if (ARGV.length < 1 || ARGV.length > 2) 33 | puts "usage: ruby test.rb " 34 | puts "examples:" 35 | puts ' ruby test.rb "/Applications"' 36 | puts ' ruby test.rb "/Applications/Max.app" --- you can give the path to the max application directly' 37 | puts ' ruby test.rb "C:\Program Files\Cycling \'74\Max 8"' 38 | puts ' ruby test.rb "/Applications/Max8 i386" --- For MacOS, you can specify the arch to run ("i386" for Intel, "arm64" for M1)' 39 | puts 40 | exit; 41 | end 42 | 43 | @maxfolder = ARGV[0] 44 | @arch = ARGV[1] 45 | @noexit = false 46 | @noexit = true if ARGV.length > 1 47 | 48 | 49 | 50 | curdir = Dir.pwd 51 | Dir.chdir @maxfolder 52 | puts 'MAX FOLDER' 53 | puts Dir.pwd 54 | @maxfolder = Dir.pwd 55 | Dir.chdir curdir 56 | 57 | 58 | ################################################################### 59 | # initialization 60 | ################################################################### 61 | 62 | @host = 'localhost' 63 | @receivePort = 4792 # This must match the port-send number in testpackage-config.json 64 | @sendPort = 4791 # This must match the port-listen number in testpackage-config.json 65 | @passes = 0 66 | @failures = 0 67 | 68 | puts " Starting the OSC Server..." 69 | puts 70 | @oscReceiver = OSC::UDPServer.new 71 | @oscReceiver.bind @host, @receivePort 72 | @oscSender = OSC::UDPSocket.new 73 | @testdbPath = "" 74 | @starttime = Time.now.iso8601.chop.chop.chop.chop.chop.chop 75 | 76 | ################################################################### 77 | # sub routines 78 | ################################################################### 79 | 80 | def establishCommunication 81 | @pingReturned = 0 82 | 83 | @oscReceiver.add_method('/testdb/path', 's') do |msg| 84 | @testdbPath = msg.args[0] 85 | end 86 | 87 | @oscReceiver.add_method('/ping/return', '') do |msg| 88 | puts " Ping successfully returned." 89 | puts "" 90 | 91 | @oscSender.send(OSC::Message.new('/testdb/path?'), 0, @host, @sendPort) 92 | sleep 1 93 | @pingReturned = 1 94 | end 95 | 96 | Thread.new do 97 | @oscReceiver.serve 98 | end 99 | sleep 5 100 | 101 | ping = OSC::Message.new('/ping'); 102 | while @pingReturned == 0 103 | puts " Sending ping to Max." 104 | @oscSender.send(ping, 0, @host, @sendPort) 105 | sleep 2 106 | end 107 | end 108 | 109 | 110 | def waitOnDatabase 111 | @dbReady = 0 112 | 113 | @oscReceiver.add_method('/db/ready', '') do |msg| 114 | @dbReady = 1 115 | end 116 | 117 | Thread.new do 118 | @oscReceiver.serve 119 | end 120 | sleep 10 121 | 122 | ping = OSC::Message.new('/db/ready?'); 123 | while @dbReady == 0 124 | puts " Sending query to Max." 125 | @oscSender.send(ping, 0, @host, @sendPort) 126 | sleep 10 127 | end 128 | end 129 | 130 | 131 | def waitForTestCompletion 132 | @testCompleted = 0 133 | 134 | @oscReceiver.add_method('/test/complete', '') do |msg| 135 | @testCompleted = 1 136 | end 137 | 138 | Thread.new do 139 | @oscReceiver.serve 140 | end 141 | 142 | while @testCompleted == 0 143 | sleep 1 144 | end 145 | end 146 | 147 | 148 | def waitForAllTestCompletion 149 | @testCompleted = 0 150 | 151 | @oscReceiver.add_method('/test/all/complete', '') do |msg| 152 | @testCompleted = 1 153 | end 154 | 155 | Thread.new do 156 | @oscReceiver.serve 157 | end 158 | 159 | while @testCompleted == 0 160 | sleep 1 161 | end 162 | end 163 | 164 | 165 | def launchMax 166 | if RUBY_PLATFORM.match(/darwin/) 167 | archcmd = "" 168 | archcmd << "arch -arch x86_64" if @arch == 'x86_64' 169 | archcmd << "arch -arch arm64" if @arch == 'arm64' 170 | if @maxfolder.match(/\.app\/*$/) # check if app name given directly 171 | IO.popen("#{archcmd} \"#{@maxfolder}/Contents/MacOS/Max\"") 172 | else # nope, just a folder name, so assume Max.app 173 | IO.popen("#{archcmd} \"#{@maxfolder}/Max.app/Contents/MacOS/Max\"") 174 | end 175 | else 176 | IO.popen "\"#{@maxfolder}/Max.exe\"" 177 | end 178 | end 179 | 180 | 181 | ################################################################### 182 | # here is where we actually run the tests 183 | ################################################################### 184 | 185 | begin 186 | Timeout::timeout(120) { # For each phase, set a simple timeout so that we can exit test script if Max is not responding. 187 | puts " Launching Max..." 188 | launchMax() 189 | } 190 | rescue Timeout::Error 191 | estring << "\n\n Max could not be launched." 192 | testpass = "fail" 193 | end 194 | 195 | begin 196 | if testpass != "fail" 197 | Timeout::timeout(120) { 198 | puts " Establishing Communication with Max..." 199 | establishCommunication() 200 | } 201 | end 202 | rescue Timeout::Error 203 | estring << "\n\n Communication with Max could not be established." 204 | testpass = "fail" 205 | end 206 | 207 | begin 208 | if testpass != "fail" 209 | Timeout::timeout(600) { 210 | puts " Waiting for the Max database to complete..." 211 | waitOnDatabase() 212 | } 213 | end 214 | rescue Timeout::Error 215 | estring << "\n\n Max Database harvesting did not complete." 216 | testpass = "fail" 217 | end 218 | 219 | begin 220 | if testpass != "fail" 221 | Timeout::timeout(600) { 222 | puts 223 | puts " Telling Max to run all of the tests for us..." 224 | mess = OSC::Message.new "test.master run" 225 | @oscSender.send(mess, 0, @host, @sendPort) 226 | waitForAllTestCompletion() 227 | } 228 | end 229 | rescue Timeout::Error 230 | estring << "\n\n Max was interrupted during tests." 231 | testpass = "fail" 232 | end 233 | 234 | 235 | mess = OSC::Message.new 'max quit' 236 | @oscSender.send(mess, 0, @host, @sendPort) 237 | 238 | puts 239 | puts " RESULTS" 240 | 241 | sleep 5 # hack -- the db might still be open because it doesn't get flushed in a quittask... 242 | 243 | db = SQLite3::Database.new( "#{@testdbPath}" ) 244 | 245 | testcount = db.execute("SELECT test_name FROM tests WHERE test_start >= Datetime('#{@starttime}') ").length 246 | assertcount = db.execute("SELECT assertion_name FROM assertions WHERE assertion_finish >= Datetime('#{@starttime}') ").length 247 | 248 | estring << "\n" 249 | estring << " Executed #{testcount} Tests with #{assertcount} Assertions" 250 | failed_assertions = db.execute("SELECT assertion_name FROM assertions WHERE assertion_value != 'Pass' AND assertion_finish >= Datetime('#{@starttime}')").length 251 | if (failed_assertions == 0 && testpass != "fail") 252 | estring << "\n All assertions passed. Congratulations!" 253 | testpass = "pass" 254 | else 255 | failed_tests = Hash.new 256 | testpass = "fail" 257 | db.execute("SELECT assertion_id, test_id_ext, assertion_name FROM assertions WHERE assertion_value != 'Pass' AND assertion_finish >= Datetime('#{@starttime}')") do |row| 258 | failed_tests[row[1]] = true; 259 | end 260 | 261 | estring << "\n #{failed_assertions} assertion(s) failed in #{failed_tests.length} test(s)" 262 | failed_tests.each do |test_id, unused| 263 | testname = db.execute("SELECT test_name FROM tests WHERE test_id = #{test_id}") 264 | estring << "\n\n FAILED TEST ( #{testname} )" 265 | db.execute( "SELECT assertion_id, assertion_name, assertion_value, assertion_finish FROM assertions WHERE test_id_ext = #{test_id}" ) do |row| 266 | estring << "\n assertion: #{row[1]} result: #{row[2]} #{'****' if row[2]!='Pass'}" 267 | end 268 | db.execute( "SELECT log_id, text, timestamp FROM logs WHERE test_id_ext = #{test_id}" ) do |row| 269 | estring << "\n log: #{row[1]}" 270 | end 271 | end 272 | end 273 | 274 | estring << "\n\n" 275 | # export results so a caller of this script is able to access the summary for e.g. automated email delivery 276 | ENV['MAXTEST'] = estring # log 277 | ENV['MAXTEST_PASS'] = testpass # general pass/fail 278 | 279 | puts estring 280 | puts testpass 281 | 282 | puts " Full test results can be found @ " 283 | puts " #{@testdbPath} " 284 | puts " and explored in your favorite SQLite database client." 285 | puts 286 | 287 | @oscSender.close # don't forget to close the ports! 288 | @oscReceiver.close 289 | 290 | # Must explicitly exit or we end up with a zombie process due to un-joined threads 291 | exit 0 if !@noexit 292 | -------------------------------------------------------------------------------- /source/projects/oscar/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | 3 | set(C74_MAX_SDK_BASE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../max-sdk-base) 4 | set(C74_BUILD_MAX_EXTENSION true) 5 | include(${C74_MAX_SDK_BASE_DIR}/script/max-pretarget.cmake) 6 | 7 | 8 | include_directories( 9 | "${C74_INCLUDES}" 10 | ) 11 | 12 | 13 | add_library( 14 | ${PROJECT_NAME} 15 | MODULE 16 | ${MAX_SDK_INCLUDES}/common/commonsyms.c 17 | ext_test.cpp 18 | ext_test.h 19 | oscar.c 20 | oscar.h 21 | test.assert.cpp 22 | test.db.cpp 23 | test.equals.cpp 24 | test.log.cpp 25 | test.master.c 26 | test.port.cpp 27 | test.runner.c 28 | test.sample~.cpp 29 | test.terminate.cpp 30 | test.unit.c 31 | ) 32 | 33 | 34 | include(${C74_MAX_SDK_BASE_DIR}/script/max-posttarget.cmake) 35 | 36 | -------------------------------------------------------------------------------- /source/projects/oscar/ext_test.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | ext_test.h 3 | Copyright 2009 - Cycling '74 4 | Timothy Place, tim@cycling74.com 5 | */ 6 | 7 | #include "ext_test.h" 8 | #include "ext_strings.h" 9 | #include "jpatcher_api.h" 10 | 11 | 12 | void test_assert(t_test *t, const char *name, t_bool passed, t_symbol **tags, long tag_count) 13 | { 14 | object_method((t_object*)t, gensym("assert"), name, passed, tags, tag_count); 15 | } 16 | 17 | 18 | void test_log(t_test *t, const char *text, ...) 19 | { 20 | va_list ap; 21 | char str[MAX_PATH_CHARS]; // MAX_PATH_CHARS just choosen for a nice comfortable size 22 | size_t len; 23 | 24 | va_start(ap, text); 25 | len = vsnprintf(str, MAX_PATH_CHARS, text, ap); 26 | str[MAX_PATH_CHARS-1] = 0; 27 | object_method((t_object*)t, gensym("log"), str); 28 | va_end(ap); 29 | } 30 | -------------------------------------------------------------------------------- /source/projects/oscar/ext_test.h: -------------------------------------------------------------------------------- 1 | /* 2 | ext_test.h 3 | Copyright 2009 - Cycling '74 4 | Timothy Place, tim@cycling74.com 5 | */ 6 | 7 | #pragma once 8 | 9 | #include "ext.h" 10 | #include "ext_obex.h" 11 | 12 | #ifdef WIN_VERSION 13 | #ifndef snprintf 14 | #define snprintf _snprintf 15 | #endif 16 | #endif 17 | 18 | 19 | /** A test object. 20 | This object is passed to test methods to provide a means by which the test can communicate with the caller of the test. 21 | @ingroup testing */ 22 | typedef t_object t_test; 23 | 24 | 25 | /** Possible test method return values. 26 | @ingroup testing */ 27 | typedef enum _testresultvalue { 28 | kTestResult_Passed = 0, ///< Passed 29 | kTestResult_FailedGeneric, ///< Failed 30 | kTestResult_FailedNoSuchObject, ///< Failed, specifically because the object could not be found. 31 | kTestResult_FailedNoSuchTest ///< Failed, specifically because no such test could be found. 32 | } t_testvalue; 33 | 34 | 35 | BEGIN_USING_C_LINKAGE 36 | 37 | 38 | /** Assert that the result of an operation meets expectations. 39 | @ingroup testing 40 | @param t The testrunner object calling our test method. 41 | @param name The name of the assertion. 42 | @param passed Pass true if the assertion passes, or false if it fails. 43 | @param tags Any user-defined tags that could be used to help search the results database. 44 | @param tag_count Number of tags. 45 | */ 46 | void test_assert(t_test *t, const char *name, t_bool passed, t_symbol **tags, long tag_count); 47 | 48 | 49 | /** Log some text in the search results. 50 | @ingroup testing 51 | @param t The testrunner object calling our test method. 52 | @param text Text to be added to the test log. */ 53 | void test_log(t_test *t, const char *text, ...); 54 | 55 | 56 | END_USING_C_LINKAGE 57 | -------------------------------------------------------------------------------- /source/projects/oscar/oscar.c: -------------------------------------------------------------------------------- 1 | // 2 | // The oscar extension for max 3 | // 4 | // Oscar is the first name of the man commonly known as The Wizard of Oz 5 | // (we are pulling the strings of Max to automate its operation) 6 | // Oscar is also the name of the classic Grouch on Sesame Street 7 | // (because testing is an activity that can make us grumpy) 8 | // 9 | // Tim Place 10 | // Cycling '74 11 | // 12 | 13 | #include "oscar.h" 14 | 15 | 16 | // Globals 17 | t_symbol *ps_testmaster; 18 | t_symbol *ps_testport; 19 | t_atom_long g_port_send = 0; 20 | t_atom_long g_port_listen = 0; 21 | 22 | 23 | // Entry 24 | void ext_main(void *r) 25 | { 26 | common_symbols_init(); 27 | 28 | testmaster_classinit(); 29 | testrunner_classinit(); 30 | testunit_classinit(); 31 | testdb_classinit(); 32 | testport_classinit(); 33 | testassert_classinit(); 34 | testequals_classinit(); 35 | testlog_classinit(); 36 | testterminate_classinit(); 37 | testsample_classinit(); 38 | 39 | ps_testmaster = gensym("test.master"); 40 | ps_testport = gensym("test.port"); 41 | 42 | ps_testmaster->s_thing = (t_object*)object_new_typed(_sym_nobox, ps_testmaster, 0, NULL); 43 | 44 | defer_low(ps_testmaster->s_thing, (method)deferred_startup, NULL, 0, NULL); 45 | quittask_install((method)testmaster_quittask, NULL); 46 | 47 | object_method(gensym("max")->s_thing, gensym("setmirrortoconsole"), 1); 48 | } 49 | 50 | 51 | void deferred_startup(void) 52 | { 53 | t_atom a[2]; 54 | 55 | atom_setlong(a+0, g_port_send); 56 | atom_setlong(a+1, g_port_listen); 57 | 58 | // cannot load classes from disk at the time that the extensions folder is processed 59 | ps_testport->s_thing = (t_object*)object_new_typed(_sym_nobox, ps_testport, 2, a); 60 | 61 | // notify anyone who is listening (e.g. a ruby script that launched max) that we are ready 62 | object_method(ps_testport->s_thing, _sym_send, gensym("/testport/ready"), 0, NULL); 63 | } 64 | 65 | 66 | // Load an external for internal use 67 | t_max_err loadextern(t_symbol *objectname, long argc, t_atom *argv, t_object **object) 68 | { 69 | t_class *c = NULL; 70 | t_object *p = NULL; 71 | 72 | c = class_findbyname(_sym_box, objectname); 73 | if (!c) { 74 | p = (t_object*)newinstance(objectname, 0, NULL); 75 | if(p){ 76 | c = class_findbyname(_sym_box, objectname); 77 | freeobject(p); 78 | p = NULL; 79 | } 80 | else{ 81 | error("could not load extern (%s) within the oscar extension", objectname->s_name); 82 | return MAX_ERR_GENERIC; 83 | } 84 | } 85 | 86 | if (*object != NULL) { // if there was an object set previously, free it first... 87 | object_free(*object); 88 | *object = NULL; 89 | } 90 | 91 | *object = (t_object*)object_new_typed(_sym_box, objectname, argc, argv); 92 | return MAX_ERR_NONE; 93 | } 94 | 95 | 96 | void autocolorbox(t_object *x) 97 | { 98 | double color[4] = {0.7, 0.4, 0.3, 1.0}; 99 | t_object *box = NULL; 100 | 101 | object_obex_lookup(x, _sym_pound_B, &box); 102 | object_attr_setdouble_array(box, _sym_color, 4, color); 103 | } 104 | 105 | -------------------------------------------------------------------------------- /source/projects/oscar/oscar.h: -------------------------------------------------------------------------------- 1 | // 2 | // The oscar extension for max 3 | // 4 | // Oscar is the first name of the man commonly known as The Wizard of Oz 5 | // (we are pulling the strings of Max to automate its operation) 6 | // Oscar is also the name of the classic Grouch on Sesame Street 7 | // (because testing is an activity that can make us grumpy) 8 | // OSCar uses OSC to communicate 9 | // 10 | // Tim Place 11 | // Cycling '74 12 | // 13 | 14 | #include "ext.h" 15 | #include "ext_obex.h" 16 | #include "ext_common.h" 17 | #include "ext_strings.h" 18 | #include "ext_critical.h" 19 | #include "ext_database.h" 20 | #include "ext_packages.h" 21 | #include "ext_test.h" 22 | 23 | BEGIN_USING_C_LINKAGE 24 | extern t_symbol *ps_testmaster; 25 | extern t_symbol *ps_testport; 26 | extern t_atom_long g_port_send; 27 | extern t_atom_long g_port_listen; 28 | extern char g_dbpath[MAX_PATH_CHARS]; 29 | 30 | void deferred_startup(void); 31 | t_max_err loadextern(t_symbol *objectname, long argc, t_atom *argv, t_object **object); 32 | void autocolorbox(t_object *x); 33 | t_object *gettoplevelpatcher(t_object *patcher); 34 | 35 | END_USING_C_LINKAGE 36 | 37 | 38 | /************************************************************************/ 39 | #if 0 40 | #pragma mark - 41 | #pragma mark test.master 42 | #endif 43 | 44 | /** Class that manages the whole shebang and provides a global entry point */ 45 | typedef struct _testmaster { 46 | t_object m_ob; ///< header 47 | t_object *m_db; ///< test.db object 48 | t_object *m_testrunner; ///< test.runner instance 49 | 50 | // unit test members: 51 | long m_object_name_count; ///< number of objects under test 52 | t_symbol **m_object_names; ///< names of the objects under test 53 | long m_test_count; ///< number of named tests to execute 54 | t_symbol **m_test_names; ///< names of the tests 55 | 56 | // integration test members: 57 | long m_integration_name_count; ///< number of test patchers 58 | t_symbol **m_integration_names; ///< names of the test patchers 59 | t_qelem *m_integration_qelem; ///< wait for one test to finish before starting the next 60 | long m_first_iter; ///< is this the first iteration through the qelem? 61 | 62 | } t_testmaster; 63 | 64 | 65 | BEGIN_USING_C_LINKAGE 66 | 67 | void testmaster_classinit(void); 68 | void testmaster_quittask(void); 69 | void* testmaster_new(t_symbol *s, long argc, t_atom *argv); 70 | void testmaster_free(t_testmaster *m); 71 | void testmaster_integration_recurse(t_testmaster *m); 72 | void testmaster_integration(t_testmaster *m, t_symbol *s, long argc, t_atom *argv); 73 | void testmaster_run(t_testmaster *m, t_symbol *s, long argc, t_atom *argv); 74 | 75 | END_USING_C_LINKAGE 76 | 77 | 78 | /************************************************************************/ 79 | #if 0 80 | #pragma mark - 81 | #pragma mark test.db 82 | #endif 83 | 84 | /** Class that wraps a database for logging test results and statistics */ 85 | typedef struct _testdb { 86 | t_object d_ob; ///< header 87 | t_database *d_db; ///< test results are written to this database for persistence 88 | } t_testdb; 89 | 90 | 91 | BEGIN_USING_C_LINKAGE 92 | 93 | void testdb_classinit(void); 94 | void* testdb_new(t_symbol *name, long argc, t_atom *argv); 95 | void testdb_free(t_testdb *d); 96 | void testdb_setup(t_testdb *d); 97 | long testdb_createcase(t_testdb *d, const char* test_name); 98 | void testdb_closecase(t_testdb *d, long test_id); 99 | void testdb_log(t_testdb *d, long test_id, const char* text, ...); 100 | 101 | END_USING_C_LINKAGE 102 | 103 | 104 | /************************************************************************/ 105 | #if 0 106 | #pragma mark - 107 | #pragma mark test.runner 108 | #endif 109 | 110 | /** Class that actually executes any one given test. */ 111 | typedef struct _testrunner { 112 | t_object r_ob; ///< header 113 | t_symbol *r_testnames[256]; ///< names of the tests to run 114 | long r_numtestnames; ///< number of tests to run 115 | long r_testid; ///< id of the test in the database 116 | long r_running; ///< currently running a test (used for waiting on integration tests) 117 | t_qelem *r_qelem; ///< used for waiting on integration test completion 118 | t_qelem *r_qelem_iter; ///< used for iterating through integration tests 119 | long r_terminated; ///< integration test has terminated 120 | t_test *r_testunit; ///< the test currently running 121 | } t_testrunner; 122 | 123 | 124 | BEGIN_USING_C_LINKAGE 125 | 126 | void testrunner_classinit(void); 127 | void* testrunner_new(t_symbol *s, long argc, t_atom *argv); 128 | void testrunner_free(t_testrunner *r); 129 | void testrunner_notify(t_testrunner *r, t_symbol *s, t_symbol *msg, void *sender, void *data); 130 | void testrunner_one_integration(t_testrunner *r, t_symbol *testname); 131 | void testrunner_integration(t_testrunner *r); 132 | void testrunner_dointegration(t_testrunner *r); 133 | 134 | END_USING_C_LINKAGE 135 | 136 | 137 | /************************************************************************/ 138 | #if 0 139 | #pragma mark - 140 | #pragma mark test.port 141 | #endif 142 | 143 | /** Class providing a udp portal for remote communication with Max. */ 144 | typedef struct _testport { 145 | t_object u_ob; ///< header 146 | t_object *u_udpreceive; ///< udpreceive instance 147 | t_object *u_udpsend; ///< udpsend instance 148 | } t_testport; 149 | 150 | 151 | BEGIN_USING_C_LINKAGE 152 | 153 | void testport_classinit(void); 154 | void* testport_new(t_symbol *s, long argc, t_atom *argv); 155 | void testport_free(t_testport *u); 156 | t_max_err testport_notify(t_testport *u, t_symbol *s, t_symbol *msg, void *sender, void *data); 157 | t_max_err testport_send(t_testport *u, t_symbol *msg, long argc, t_atom *argv); 158 | void testport_ping(t_testport *u); 159 | 160 | END_USING_C_LINKAGE 161 | 162 | 163 | /************************************************************************/ 164 | #if 0 165 | #pragma mark - 166 | #pragma mark test.unit 167 | #endif 168 | 169 | /** Class that is passed to tests when they are run to provide hooks back into the system. */ 170 | typedef struct _testunit { 171 | t_object o_ob; ///< header 172 | t_testdb *o_db; ///< database interface for logging results 173 | long o_id; ///< database id for this test 174 | } t_testunit; 175 | 176 | 177 | BEGIN_USING_C_LINKAGE 178 | 179 | void testunit_classinit(void); 180 | void* testunit_new(t_symbol *s, long argc, t_atom *argv); 181 | void testunit_free(t_testunit *o); 182 | void testunit_log(t_testunit *u, const char* text); 183 | void testunit_assert(t_testunit *u, const char* assertion_name, t_bool passed, t_symbol **tags, long tag_count); 184 | void testunit_terminate(t_testunit *u); 185 | 186 | END_USING_C_LINKAGE 187 | 188 | 189 | /************************************************************************/ 190 | #if 0 191 | #pragma mark - 192 | #pragma mark test.assert 193 | #endif 194 | 195 | enum { 196 | TEST_ASSERT_NOT_EXECUTED = 0, 197 | TEST_ASSERT_PASS, 198 | TEST_ASSERT_FAIL 199 | }; 200 | 201 | #define MAX_TAG_COUNT 16 202 | 203 | /** Class for instrumenting patchers that are used to execute tests 204 | so that they can communicate results back to the testrunner. */ 205 | typedef struct _testassert { 206 | t_object a_ob; ///< header 207 | void *a_outlet; ///< outlet for providing input to the system under test 208 | t_symbol *a_name; ///< name of the assertion 209 | t_atom *a_input; ///< input to the system 210 | long a_inputcount; ///< number of atoms in a_input 211 | t_atom *a_output; ///< expected output to the system 212 | long a_outputcount; ///< number of atoms in a_output 213 | long a_status; ///< pass or fail status of the assertion 214 | t_test *a_test; ///< test object that is calling this assertion 215 | t_bool a_passed; ///< result 216 | t_symbol *a_tags[MAX_TAG_COUNT]; ///< any user-specified tags to be associated with the assertion for searching test results 217 | long a_tagcount; ///< number of tags 218 | } t_testassert; 219 | 220 | 221 | BEGIN_USING_C_LINKAGE 222 | 223 | void testassert_classinit(void); 224 | void* testassert_new(t_symbol *s, long argc, t_atom *argv); 225 | void testassert_free(t_testassert *x); 226 | void testassert_assist(t_testassert *x, void *b, long m, long a, char *s); 227 | void testassert_loadbang(t_testassert *x); 228 | void testassert_int(t_testassert *x, long v); 229 | void testassert_float(t_testassert *x, double v); 230 | void testassert_anything(t_testassert *x, t_symbol *s, long argc, t_atom *argv); 231 | void testassert_pop(t_testassert *x); 232 | 233 | END_USING_C_LINKAGE 234 | 235 | 236 | /************************************************************************/ 237 | #if 0 238 | #pragma mark - 239 | #pragma mark test.equals 240 | #endif 241 | 242 | BEGIN_USING_C_LINKAGE 243 | void testequals_classinit(void); 244 | END_USING_C_LINKAGE 245 | 246 | 247 | /************************************************************************/ 248 | #if 0 249 | #pragma mark - 250 | #pragma mark test.log 251 | #endif 252 | 253 | 254 | /** Class for instrumenting patchers that are used to execute tests 255 | so that they can communicate results back to the testrunner. */ 256 | typedef struct _testlog { 257 | t_object a_ob; ///< header 258 | t_test *a_test; ///< test object 259 | } t_testlog; 260 | 261 | 262 | BEGIN_USING_C_LINKAGE 263 | 264 | void testlog_classinit(void); 265 | void* testlog_new(t_symbol *s, long argc, t_atom *argv); 266 | void testlog_free(t_testlog *x); 267 | void testlog_assist(t_testlog *x, void *b, long m, long a, char *s); 268 | void testlog_int(t_testlog *x, long v); 269 | void testlog_float(t_testlog *x, double v); 270 | void testlog_anything(t_testlog *x, t_symbol *s, long argc, t_atom *argv); 271 | 272 | END_USING_C_LINKAGE 273 | 274 | 275 | /************************************************************************/ 276 | #if 0 277 | #pragma mark - 278 | #pragma mark test.terminate 279 | #endif 280 | 281 | /** Class for instrumenting patchers that are used to execute tests 282 | so that they can communicate results back to the testrunner. */ 283 | typedef struct _testterminate { 284 | t_object x_ob; ///< header 285 | t_object *x_patcher; ///< the patcher in which the object exists -- assumed to be a top-level patcher 286 | t_test *x_test; ///< test object that is calling this assertion 287 | } t_testterminate; 288 | 289 | 290 | BEGIN_USING_C_LINKAGE 291 | 292 | void testterminate_classinit(void); 293 | void* testterminate_new(t_symbol *s, long argc, t_atom *argv); 294 | void testterminate_free(t_testterminate *x); 295 | void testterminate_assist(t_testterminate *x, void *b, long m, long a, char *s); 296 | void testterminate_bang(t_testterminate *x); 297 | 298 | END_USING_C_LINKAGE 299 | 300 | 301 | /************************************************************************/ 302 | #if 0 303 | #pragma mark - 304 | #pragma mark test.sample~ 305 | #endif 306 | 307 | BEGIN_USING_C_LINKAGE 308 | 309 | void testsample_classinit(void); 310 | 311 | END_USING_C_LINKAGE 312 | -------------------------------------------------------------------------------- /source/projects/oscar/test.assert.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // unit/integration test extension for max 3 | // tim place 4 | // cycling '74 5 | // 6 | 7 | #include "oscar.h" 8 | 9 | 10 | // class variables 11 | static t_class *s_testassert_class = NULL; 12 | t_linklist *g_all_assert_instances = NULL; 13 | 14 | 15 | /************************************************************************/ 16 | 17 | void testassert_classinit(void) 18 | { 19 | t_class *c = class_new("test.assert", 20 | (method)testassert_new, 21 | (method)testassert_free, 22 | sizeof(t_testassert), 23 | (method)NULL, 24 | A_GIMME, 25 | 0L); 26 | 27 | class_addmethod(c, (method)testassert_int, "int", A_LONG, 0); 28 | class_addmethod(c, (method)testassert_float, "float", A_FLOAT, 0); 29 | class_addmethod(c, (method)testassert_anything, "list", A_GIMME, 0); 30 | class_addmethod(c, (method)testassert_anything, "anything", A_GIMME, 0); 31 | class_addmethod(c, (method)testassert_pop, "pop", A_CANT, 0); 32 | class_addmethod(c, (method)testassert_loadbang, "loadbang", A_CANT, 0); 33 | class_addmethod(c, (method)testassert_assist, "assist", A_CANT, 0); 34 | 35 | CLASS_ATTR_SYM_VARSIZE(c, "tags", 0, t_testassert, a_tags, a_tagcount, MAX_TAG_COUNT); 36 | 37 | class_register(_sym_box, c); 38 | s_testassert_class = c; 39 | } 40 | 41 | 42 | /************************************************************************/ 43 | 44 | 45 | t_object *gettoplevelpatcher(t_object *patcher) 46 | { 47 | t_object *toplevelpatcher = patcher; 48 | 49 | while ((patcher = object_attr_getobj(patcher, _sym_parentpatcher))) 50 | toplevelpatcher = patcher; 51 | 52 | return toplevelpatcher; 53 | } 54 | 55 | 56 | 57 | void* testassert_new(t_symbol *s, long argc, t_atom *argv) 58 | { 59 | t_testassert *x = (t_testassert*)object_alloc(s_testassert_class); 60 | long attrstart = attr_args_offset((short)argc, argv); 61 | 62 | if (x) { 63 | x->a_outlet = outlet_new(x, NULL); 64 | x->a_test = (t_test*)gensym("#T")->s_thing; 65 | if (attrstart) 66 | x->a_name = atom_getsym(argv); 67 | else 68 | x->a_name = symbol_unique(); 69 | attr_args_process(x, (short)argc, argv); 70 | } 71 | 72 | if (!g_all_assert_instances) 73 | g_all_assert_instances = linklist_new(); 74 | linklist_append(g_all_assert_instances, x); 75 | 76 | autocolorbox((t_object*)x); 77 | return x; 78 | } 79 | 80 | 81 | void testassert_free(t_testassert *x) 82 | { 83 | linklist_chuckobject(g_all_assert_instances, x); 84 | } 85 | 86 | 87 | #pragma mark - 88 | /************************************************************************/ 89 | 90 | void testassert_assist(t_testassert *x, void *b, long m, long a, char *s) 91 | { 92 | if (m==ASSIST_INLET) { 93 | switch (a) { 94 | case 0: sprintf(s,"receive values from the system under test to compare against expectations"); break; 95 | } 96 | } 97 | else { 98 | switch (a) { 99 | case 0: sprintf(s,"sends input values to the system under test at loadbang time"); break; 100 | } 101 | } 102 | } 103 | 104 | 105 | void testassert_loadbang(t_testassert *x) 106 | { 107 | if (x->a_inputcount) { 108 | if (atom_gettype(x->a_input) == A_LONG || atom_gettype(x->a_input) == A_FLOAT) { 109 | if (x->a_inputcount > 1) 110 | outlet_anything(x->a_outlet, _sym_list, (short)x->a_inputcount, x->a_input); 111 | else if(atom_gettype(x->a_input) == A_LONG) 112 | outlet_int(x->a_outlet, atom_getlong(x->a_input)); 113 | else 114 | outlet_float(x->a_outlet, atom_getfloat(x->a_input)); 115 | } 116 | else 117 | outlet_anything(x->a_outlet, atom_getsym(x->a_input), (short)x->a_inputcount-1, x->a_input+1); 118 | } 119 | } 120 | 121 | 122 | void testassert_int(t_testassert *x, long v) 123 | { 124 | if (!x->a_test) 125 | return; 126 | 127 | if (x->a_inputcount == 0) { // by default we just look for 0 (fail) or 1 (pass) 128 | if (v == 1) { 129 | x->a_status = TEST_ASSERT_PASS; 130 | x->a_passed = true; 131 | } 132 | else { 133 | x->a_status = TEST_ASSERT_FAIL; 134 | x->a_passed = false; 135 | } 136 | } 137 | else { 138 | t_atom a[1]; 139 | 140 | atom_setlong(a, v); 141 | testassert_anything(x, _sym_int, 1, a); 142 | } 143 | } 144 | 145 | 146 | void testassert_float(t_testassert *x, double v) 147 | { 148 | t_atom a[1]; 149 | 150 | atom_setfloat(a, v); 151 | testassert_anything(x, _sym_float, 1, a); 152 | } 153 | 154 | 155 | void testassert_anything(t_testassert *x, t_symbol *s, long argc, t_atom *argv) 156 | { 157 | if (!x->a_test) 158 | return; 159 | 160 | // 1. compare input to expected output 161 | 162 | // 2. save the result 163 | x->a_status = TEST_ASSERT_FAIL; 164 | test_assert(x->a_test, x->a_name->s_name, false, (t_symbol**)x->a_tags, x->a_tagcount); 165 | } 166 | 167 | 168 | // test is being closed down and the assertions are being popped, so log the result before we disappear... 169 | 170 | void testassert_pop(t_testassert *x) 171 | { 172 | if (!x->a_test) 173 | return; 174 | 175 | if (x->a_status == TEST_ASSERT_NOT_EXECUTED) { 176 | test_log(x->a_test, "assertion '%s' never returned results!", x->a_name->s_name); 177 | } 178 | test_assert(x->a_test, x->a_name->s_name, x->a_passed, (t_symbol**)x->a_tags, x->a_tagcount); 179 | } 180 | -------------------------------------------------------------------------------- /source/projects/oscar/test.db.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // unit test extension for max 3 | // tim place 4 | // cycling '74 5 | // 6 | 7 | #include "oscar.h" 8 | 9 | 10 | // class variables 11 | static t_class *s_testdb_class = NULL; 12 | char g_dbpath[MAX_PATH_CHARS]; 13 | 14 | 15 | /************************************************************************/ 16 | 17 | void testdb_classinit(void) 18 | { 19 | t_class *c = class_new("test.db", (method)testdb_new, (method)testdb_free, sizeof(t_testdb), (method)NULL, A_GIMME, 0L); 20 | class_register(_sym_nobox, c); 21 | s_testdb_class = c; 22 | } 23 | 24 | 25 | void* testdb_new(t_symbol *name, long argc, t_atom *argv) 26 | { 27 | t_testdb *d = (t_testdb*)object_alloc(s_testdb_class); 28 | 29 | if (d) { 30 | attr_args_process(d, (short)argc, argv); 31 | testdb_setup(d); 32 | } 33 | return d; 34 | } 35 | 36 | 37 | void testdb_free(t_testdb *d) 38 | { 39 | db_close(&d->d_db); 40 | } 41 | 42 | 43 | void testdb_setup(t_testdb *d) 44 | { 45 | if (!d->d_db) { 46 | short path = packages_getpackagepath("max-test"); 47 | char fullpath[MAX_PATH_CHARS]; 48 | short apppath = path_getapppath(); 49 | char appfullpath[MAX_PATH_CHARS]; 50 | unsigned long appfullpathlen; 51 | int i; 52 | char dbfilename[MAX_PATH_CHARS]; 53 | t_db_result *dbresult = NULL; 54 | 55 | path_topathname(apppath, "", appfullpath); 56 | appfullpathlen = strlen(appfullpath); 57 | for (i=0; id_db); 68 | 69 | // cache the fullpath so it can be requested from the outside world 70 | { 71 | short apath; 72 | char afilename[MAX_FILENAME_CHARS]; 73 | path_frompathname(fullpath, &apath, afilename); 74 | path_toabsolutesystempath(apath, afilename, g_dbpath); 75 | } 76 | 77 | db_query(d->d_db, &dbresult, "SELECT name FROM sqlite_master WHERE type='table' AND name='tests'"); 78 | if (!db_result_numrecords(dbresult)) { 79 | db_query_table_new(d->d_db, "tests"); 80 | db_query_table_addcolumn(d->d_db, "tests", "test_name", "VARCHAR(512)", 0); 81 | db_query_table_addcolumn(d->d_db, "tests", "test_start", "DATETIME", 0); 82 | db_query_table_addcolumn(d->d_db, "tests", "test_finish", "DATETIME", 0); 83 | 84 | db_query_table_new(d->d_db, "assertions"); 85 | db_query_table_addcolumn(d->d_db, "assertions", "test_id_ext", "INTEGER", 0); 86 | db_query_table_addcolumn(d->d_db, "assertions", "assertion_name", "VARCHAR(512)", 0); 87 | db_query_table_addcolumn(d->d_db, "assertions", "assertion_value", "VARCHAR(512)", 0); 88 | db_query_table_addcolumn(d->d_db, "assertions", "assertion_data", "VARCHAR(512)", 0); 89 | db_query_table_addcolumn(d->d_db, "assertions", "assertion_start", "DATETIME", 0); 90 | db_query_table_addcolumn(d->d_db, "assertions", "assertion_finish", "DATETIME", 0); 91 | db_query_table_addcolumn(d->d_db, "assertions", "assertion_tags", "VARCHAR(512)", 0); 92 | 93 | db_query_table_new(d->d_db, "logs"); 94 | db_query_table_addcolumn(d->d_db, "logs", "test_id_ext", "INTEGER", 0); 95 | db_query_table_addcolumn(d->d_db, "logs", "text", "VARCHAR(512)", 0); 96 | db_query_table_addcolumn(d->d_db, "logs", "timestamp", "DATETIME", 0); 97 | } 98 | object_free(dbresult); 99 | } 100 | } 101 | 102 | 103 | long testdb_createcase(t_testdb *d, const char* test_name) 104 | { 105 | long test_id = 0; 106 | t_ptr_uint timestamp = systime_seconds(); 107 | t_datetime datetime; 108 | 109 | systime_secondstodate(timestamp, &datetime); 110 | db_query(d->d_db, NULL, "INSERT INTO tests ( test_name , test_start, test_finish ) \ 111 | VALUES ( \"%s\" , '%4u-%02u-%02u %02u:%02u:%02u', 0 ) ", 112 | test_name, 113 | datetime.year, datetime.month, datetime.day, datetime.hour, datetime.minute, datetime.second); 114 | db_query_getlastinsertid(d->d_db, &test_id); 115 | return test_id; 116 | } 117 | 118 | 119 | void testdb_closecase(t_testdb *d, long test_id) 120 | { 121 | t_ptr_uint timestamp = systime_seconds(); 122 | t_datetime datetime; 123 | 124 | systime_secondstodate(timestamp, &datetime); 125 | db_query(d->d_db, NULL, "UPDATE tests SET test_finish = '%4u-%02u-%02u %02u:%02u:%02u' \ 126 | WHERE test_id = %i", 127 | datetime.year, datetime.month, datetime.day, datetime.hour, datetime.minute, datetime.second, 128 | test_id); 129 | } 130 | 131 | 132 | void testdb_log(t_testdb *d, long test_id, const char* text, ...) 133 | { 134 | t_ptr_uint timestamp = systime_seconds(); 135 | t_datetime datetime; 136 | va_list ap; 137 | char expandedtext[2048]; 138 | 139 | va_start(ap, text); 140 | vsnprintf(expandedtext, 2048, text, ap); 141 | 142 | { 143 | t_atom a; 144 | atom_setsym(&a, gensym(expandedtext)); 145 | testport_send((t_testport*)ps_testport->s_thing, gensym("/db/log"), 1, &a); 146 | } 147 | 148 | systime_secondstodate(timestamp, &datetime); 149 | db_query(d->d_db, NULL, "INSERT INTO logs ( test_id_ext , text , timestamp ) \ 150 | VALUES ( %i , \"%s\" , '%4u-%02u-%02u %02u:%02u:%02u' )", 151 | test_id, expandedtext, 152 | datetime.year, datetime.month, datetime.day, datetime.hour, datetime.minute, datetime.second); 153 | } 154 | 155 | -------------------------------------------------------------------------------- /source/projects/oscar/test.equals.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // unit/integration test extension for max 3 | // tim place 4 | // cycling '74 5 | // 6 | 7 | #include "oscar.h" 8 | #include "z_dsp.h" 9 | #ifdef WIN_VERSION 10 | #include 11 | #endif 12 | #include 13 | 14 | /** like == but can compare floating-point numbers while tolerating the floating-point (im)precision. 15 | */ 16 | typedef struct _testequals { 17 | t_pxobject x_ob; ///< header 18 | void *x_outlet; ///< float/list for sampled values 19 | void *x_inlet; ///< for setting the operand 20 | double x_operand; ///< the number against which to test input 21 | long x_tolerance; ///< number of floating-point representations around the specified operand to consider as "equal" 22 | long x_single_precision; ///< operate on 32-bit floats rather than 64-bit doubles 23 | } t_testequals; 24 | 25 | 26 | // prototypes 27 | void* testequals_new(t_symbol *s, long argc, t_atom *argv); 28 | void testequals_free(t_testequals *x); 29 | void testequals_assist(t_testequals *x, void *b, long m, long a, char *s); 30 | void testequals_float(t_testequals *x, double f); 31 | 32 | 33 | // class variables 34 | static t_class *s_testequals_class = NULL; 35 | 36 | 37 | /************************************************************************/ 38 | 39 | void testequals_classinit(void) 40 | { 41 | t_class *c = class_new("test.equals", (method)testequals_new, (method)testequals_free, sizeof(t_testequals), (method)NULL, A_GIMME, 0); 42 | 43 | class_addmethod(c, (method)testequals_float, "float", A_FLOAT, 0); 44 | class_addmethod(c, (method)testequals_assist, "assist", A_CANT, 0); 45 | 46 | CLASS_ATTR_LONG(c, "tolerance", 0, t_testequals, x_tolerance); 47 | CLASS_ATTR_LONG(c, "single_precision", 0, t_testequals, x_single_precision); 48 | 49 | class_register(_sym_box, c); 50 | s_testequals_class = c; 51 | } 52 | 53 | 54 | /************************************************************************/ 55 | 56 | 57 | void* testequals_new(t_symbol *s, long argc, t_atom *argv) 58 | { 59 | t_testequals *x = (t_testequals*)object_alloc(s_testequals_class); 60 | long attrstart = attr_args_offset((short)argc, argv); 61 | 62 | if (attrstart) 63 | x->x_operand = atom_getfloat(argv); 64 | 65 | x->x_outlet = outlet_new(x, NULL); 66 | x->x_inlet = proxy_new(x, 1, NULL); 67 | x->x_tolerance = 2; 68 | #ifdef C74_X64 69 | x->x_single_precision = false; 70 | #else 71 | x->x_single_precision = true; 72 | #endif 73 | 74 | attr_args_process(x, (short)argc, argv); 75 | autocolorbox((t_object*)x); 76 | return x; 77 | } 78 | 79 | 80 | void testequals_free(t_testequals *x) 81 | { 82 | object_free(x->x_inlet); 83 | } 84 | 85 | 86 | #pragma mark - 87 | /************************************************************************/ 88 | 89 | void testequals_assist(t_testequals *x, void *b, long m, long a, char *s) 90 | { 91 | strcpy(s, "log messages to the test result, or to the max window"); 92 | } 93 | 94 | 95 | // see http://realtimecollisiondetection.net/blog/?p=89 regarding the comparison 96 | 97 | static const double k_epsilon64 = DBL_EPSILON; 98 | static const float k_epsilon32 = FLT_EPSILON; 99 | 100 | t_bool testequals_equivalent(double a, double b, long tolerance, long single_precision) 101 | { 102 | if (single_precision) { 103 | float tol = tolerance * k_epsilon32; 104 | float maxab = (std::max)(fabsf((float)a), fabsf((float)b)); 105 | 106 | if ( fabsf((float)a - (float)b) <= tol * (std::max)(1.0f, maxab) ) 107 | return true; 108 | } 109 | else { 110 | double tol = tolerance * k_epsilon64; 111 | double maxab = (std::max)(fabs(a), fabs(b)); 112 | 113 | if ( fabs(a - b) <= tol * (std::max)(1.0, maxab) ) 114 | return true; 115 | } 116 | return false; 117 | } 118 | 119 | 120 | void testequals_float(t_testequals *x, double f) 121 | { 122 | if (proxy_getinlet((t_object*)x) == 1) 123 | x->x_operand = f; 124 | else 125 | outlet_int(x->x_outlet, testequals_equivalent(x->x_operand, f, x->x_tolerance, x->x_single_precision)); 126 | } 127 | 128 | -------------------------------------------------------------------------------- /source/projects/oscar/test.log.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // unit/integration test extension for max 3 | // tim place 4 | // cycling '74 5 | // 6 | 7 | #include "oscar.h" 8 | 9 | 10 | // class variables 11 | static t_class *s_testlog_class = NULL; 12 | 13 | 14 | /************************************************************************/ 15 | 16 | void testlog_classinit(void) 17 | { 18 | t_class *c = class_new("test.log", 19 | (method)testlog_new, 20 | (method)testlog_free, 21 | sizeof(t_testlog), 22 | (method)NULL, 23 | A_GIMME, 24 | 0L); 25 | 26 | class_addmethod(c, (method)testlog_int, "int", A_LONG, 0); 27 | class_addmethod(c, (method)testlog_float, "float", A_FLOAT, 0); 28 | class_addmethod(c, (method)testlog_anything, "list", A_GIMME, 0); 29 | class_addmethod(c, (method)testlog_anything, "anything", A_GIMME, 0); 30 | class_addmethod(c, (method)testlog_assist, "assist", A_CANT, 0); 31 | 32 | class_register(_sym_box, c); 33 | s_testlog_class = c; 34 | } 35 | 36 | 37 | /************************************************************************/ 38 | 39 | 40 | void* testlog_new(t_symbol *s, long argc, t_atom *argv) 41 | { 42 | t_testlog *x = (t_testlog*)object_alloc(s_testlog_class); 43 | 44 | if (x) { 45 | x->a_test = (t_test*)gensym("#T")->s_thing; 46 | attr_args_process(x, (short)argc, argv); 47 | } 48 | autocolorbox((t_object*)x); 49 | return x; 50 | } 51 | 52 | 53 | void testlog_free(t_testlog *x) 54 | { 55 | ; 56 | } 57 | 58 | 59 | #pragma mark - 60 | /************************************************************************/ 61 | 62 | void testlog_assist(t_testlog *x, void *b, long m, long a, char *s) 63 | { 64 | strcpy(s, "log messages to the test result, or to the max window"); 65 | } 66 | 67 | 68 | void testlog_int(t_testlog *x, long v) 69 | { 70 | t_atom a[1]; 71 | 72 | if (!x->a_test) { 73 | object_post((t_object*)x, "%i", (int)v); 74 | return; 75 | } 76 | 77 | atom_setlong(a, v); 78 | testlog_anything(x, _sym_int, 1, a); 79 | } 80 | 81 | 82 | void testlog_float(t_testlog *x, double v) 83 | { 84 | t_atom a; 85 | 86 | if (!x->a_test) { 87 | object_post((t_object*)x, "%f", v); 88 | return; 89 | } 90 | 91 | atom_setfloat(&a, v); 92 | testlog_anything(x, _sym_float, 1, &a); 93 | } 94 | 95 | 96 | void testlog_anything(t_testlog *x, t_symbol *s, long argc, t_atom *argv) 97 | { 98 | char *text = NULL; 99 | long textsize = 0; 100 | 101 | atom_gettext(argc, argv, &textsize, &text, OBEX_UTIL_ATOM_GETTEXT_DEFAULT | OBEX_UTIL_ATOM_GETTEXT_NUM_HI_RES); 102 | if (!text && textsize) { 103 | object_error((t_object*)x, "no text to log"); 104 | return; 105 | } 106 | 107 | if (x->a_test) { 108 | if (s == _sym_int || s == _sym_float || s == _sym_list) 109 | test_log(x->a_test, text); 110 | else 111 | test_log(x->a_test, "%s %s", s->s_name, text); 112 | } 113 | else { 114 | if (s == _sym_int || s == _sym_float || s == _sym_list) 115 | object_post((t_object*)x, text); 116 | else 117 | object_post((t_object*)x, "%s %s", s->s_name, text); 118 | } 119 | 120 | sysmem_freeptr(text); 121 | } 122 | 123 | -------------------------------------------------------------------------------- /source/projects/oscar/test.master.c: -------------------------------------------------------------------------------- 1 | // 2 | // unit test extension for max 3 | // tim place 4 | // cycling '74 5 | // 6 | 7 | #include "oscar.h" 8 | 9 | 10 | // class variables 11 | static t_class *s_testmaster_class = NULL; 12 | static t_object *s_testmaster_instance = NULL; 13 | 14 | 15 | /************************************************************************/ 16 | #pragma mark - 17 | #pragma mark test.master 18 | 19 | void testmaster_classinit(void) 20 | { 21 | t_class *c = class_new("test.master", (method)testmaster_new, (method)testmaster_free, sizeof(t_testmaster), (method)NULL, A_GIMME, 0L); 22 | 23 | class_addmethod(c, (method)testmaster_run, "run", A_GIMME, 0); // all tests 24 | 25 | class_register(_sym_nobox, c); 26 | s_testmaster_class = c; 27 | 28 | { 29 | char filename[MAX_FILENAME_CHARS]; 30 | short path = 0; 31 | t_fourcc type = 0; 32 | t_dictionary *d = NULL; 33 | t_max_err err; 34 | 35 | strncpy_zero(filename, "max-test-config.json", MAX_FILENAME_CHARS); 36 | locatefile_extended(filename, &path, &type, NULL, 0); 37 | err = dictionary_read(filename, path, &d); 38 | if (!err) { 39 | dictionary_getdeflong(d, gensym("port-send"), &g_port_send, 0); 40 | dictionary_getdeflong(d, gensym("port-listen"), &g_port_listen, 0); 41 | } 42 | object_free(d); 43 | } 44 | } 45 | 46 | void testmaster_quittask(void) 47 | { 48 | if (s_testmaster_instance) { 49 | object_free(s_testmaster_instance); 50 | s_testmaster_instance = NULL; 51 | ps_testmaster->s_thing = NULL; 52 | } 53 | } 54 | 55 | 56 | void testmaster_createdb(t_testmaster *m) 57 | { 58 | if (!m->m_db) 59 | m->m_db = (t_object*)testdb_new(NULL, 0, NULL); 60 | } 61 | 62 | 63 | void* testmaster_new(t_symbol *name, long argc, t_atom *argv) 64 | { 65 | t_testmaster *m = (t_testmaster*)object_alloc(s_testmaster_class); 66 | 67 | if (m) { 68 | m->m_integration_qelem = (t_qelem*)qelem_new(m, (method)testmaster_integration_recurse); 69 | attr_args_process(m, (short)argc, argv); 70 | defer_low(m, (method)testmaster_createdb, NULL, 0, NULL); // have to defer because the sqlite extension is not yet loaded 71 | } 72 | return m; 73 | } 74 | 75 | void testmaster_free(t_testmaster *m) 76 | { 77 | qelem_free(m->m_integration_qelem); 78 | object_free(m->m_testrunner); // free any old testrunner instance 79 | object_free(m->m_db); 80 | } 81 | 82 | 83 | void testmaster_integration_recurse(t_testmaster *m) 84 | { 85 | // if a test is already going, just hang out until it's done... 86 | if (m->m_testrunner && ((t_testrunner*)m->m_testrunner)->r_running) { 87 | qelem_set(m->m_integration_qelem); 88 | return; 89 | } 90 | if (!m->m_first_iter) 91 | testport_send((t_testport*)ps_testport->s_thing, gensym("/test/complete"), 0, NULL); 92 | if (m->m_integration_name_count == 0) { 93 | post("integration tests complete"); 94 | testport_send((t_testport*)ps_testport->s_thing, gensym("/test/all/complete"), 0, NULL); 95 | return; 96 | } 97 | 98 | m->m_first_iter = false; 99 | m->m_integration_name_count--; 100 | 101 | { 102 | t_symbol *testname = m->m_integration_names[m->m_integration_name_count]; 103 | char testnamestr[MAX_PATH_CHARS]; 104 | char testpath[MAX_PATH_CHARS]; 105 | short path = 0; 106 | t_fourcc type = 0; 107 | t_fourcc types[2] = {'JSON', 'mZip'}; 108 | int typecount = 2; 109 | t_atom a[2]; 110 | t_max_err err = MAX_ERR_NONE; 111 | 112 | object_free(m->m_testrunner); // free any old testrunner instance 113 | 114 | strncpy_zero(testnamestr, testname->s_name, MAX_PATH_CHARS); 115 | err = locatefile_extended(testnamestr, &path, &type, types, typecount); 116 | if (err) { 117 | error("no test found with the name '%s'", testnamestr); 118 | goto out; 119 | } 120 | 121 | path_topathname(path, testnamestr, testpath); 122 | 123 | atom_setsym(a+0, gensym("@tests")); 124 | atom_setsym(a+1, gensym(testpath)); 125 | m->m_testrunner = (t_object*)object_new_typed(_sym_nobox, gensym("test.runner"), 2, a); 126 | object_method(m->m_testrunner, gensym("integration")); 127 | 128 | // NOTE: we can't return a result directly from the 'integration' call because it is asynchronous 129 | } 130 | out: 131 | qelem_set(m->m_integration_qelem); 132 | } 133 | 134 | 135 | void testmaster_integration(t_testmaster *m, t_symbol *s, long argc, t_atom *argv) 136 | { 137 | long i; 138 | long test_count = 0; 139 | t_symbol **test_names = (t_symbol**)sysmem_newptrclear(sizeof(t_symbol*) * argc); 140 | 141 | testmaster_createdb(m); 142 | 143 | // If there are args, they specify which test patchers to run 144 | if (argc) { 145 | test_names = (t_symbol**)sysmem_newptrclear(sizeof(t_symbol*) * argc); 146 | 147 | for (i=0; i> 8) & 0xF) > 6) && (((version >> 8) & 0xF) < 15) ) { 156 | t_database *db = NULL; 157 | t_db_result *result = NULL; 158 | 159 | db_open(gensym("__maxdb__"), NULL, &db); 160 | db_query(db, &result, "SELECT DISTINCT _name FROM _things WHERE (_kind = 'patcher' OR _kind = 'project') AND _name LIKE '%%.maxtest' AND _status = ''"); 161 | test_count = db_result_numrecords(result); 162 | 163 | test_names = (t_symbol**)sysmem_newptrclear(sizeof(t_symbol*) * test_count); 164 | for (i=0; i 0) OR flags & 2))) \ 183 | AND \ 184 | name LIKE '%%.maxtest'"); 185 | test_count = db_result_numrecords(result); 186 | 187 | test_names = (t_symbol**)sysmem_newptrclear(sizeof(t_symbol*) * test_count); 188 | for (i=0; im_integration_name_count = test_count; 200 | m->m_integration_names = test_names; 201 | m->m_first_iter = true; 202 | qelem_set(m->m_integration_qelem); 203 | } 204 | 205 | 206 | void testmaster_run(t_testmaster *m, t_symbol *s, long argc, t_atom *argv) 207 | { 208 | testmaster_integration(m, s, argc, argv); 209 | } 210 | 211 | -------------------------------------------------------------------------------- /source/projects/oscar/test.port.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // unit test extension for max 3 | // tim place 4 | // cycling '74 5 | // 6 | 7 | #include "oscar.h" 8 | #include "ext_symobject.h" 9 | 10 | 11 | // class variables 12 | static t_class *s_testport_class = NULL; 13 | static t_symbol *ps_db_ready = NULL; 14 | 15 | 16 | /************************************************************************/ 17 | 18 | void testport_classinit(void) 19 | { 20 | t_class *c = class_new("test.port", (method)testport_new, (method)testport_free, sizeof(t_testport), (method)NULL, A_GIMME, 0L); 21 | 22 | class_addmethod(c, (method)testport_notify, "notify", A_CANT, 0); 23 | class_addmethod(c, (method)testport_send, "send", A_CANT, 0); 24 | class_addmethod(c, (method)testport_ping, "ping", 0); 25 | 26 | class_register(_sym_nobox, c); 27 | s_testport_class = c; 28 | ps_db_ready = gensym("###MAX_DB_READY###"); 29 | } 30 | 31 | 32 | /************************************************************************/ 33 | 34 | void* testport_new(t_symbol *s, long argc, t_atom *argv) 35 | { 36 | t_testport *u = (t_testport*)object_alloc(s_testport_class); 37 | t_max_err err = MAX_ERR_NONE; 38 | t_atom a[5]; 39 | long port_send = atom_getlong(argv); 40 | long port_listen = atom_getlong(argv+1); 41 | 42 | if (port_send == 0 || port_listen == 0) { 43 | cpost("test.port not enabled\n"); 44 | } 45 | else { 46 | attr_args_process(u, (short)argc, argv); 47 | 48 | atom_setsym(a+0, gensym("127.0.0.1")); 49 | atom_setlong(a+1, port_send); // 4792 50 | err = loadextern(gensym("udpsend"), 2, a, &u->u_udpsend); 51 | if (!err) { 52 | object_attach_byptr_register(u, u->u_udpsend, _sym_box); 53 | cpost("test.port sending on %ld \n", port_send); 54 | } 55 | else 56 | cpost("test.port could not be configured (udpsend) \n"); 57 | 58 | atom_setlong(a+0, port_listen); // 4791 59 | atom_setsym(a+1, gensym("@send_notifications")); 60 | atom_setlong(a+2, 1); 61 | atom_setsym(a+3, gensym("@quiet")); 62 | atom_setlong(a+4, 1); 63 | err = loadextern(gensym("udpreceive"), 5, a, &u->u_udpreceive); 64 | if (!err) { 65 | object_attach_byptr_register(u, u->u_udpreceive, _sym_box); 66 | cpost("test.port listening on %ld \n", port_listen); 67 | } 68 | else 69 | cpost("test.port could not be configured (udpreceive) \n"); 70 | } 71 | return u; 72 | } 73 | 74 | 75 | void testport_free(t_testport *u) 76 | { 77 | if (u->u_udpreceive) 78 | object_free(u->u_udpreceive); 79 | } 80 | 81 | 82 | // use this method to send messages from a network/remote to max or to testmaster, for example: 83 | // max quit 84 | t_max_err testport_notify(t_testport *u, t_symbol *s, t_symbol *msg, void *sender, void *data) 85 | { 86 | if (sender == u->u_udpreceive) { 87 | if (msg == _sym_message) { 88 | t_symobject *so = (t_symobject*)data; 89 | t_symbol *mess = so->sym; 90 | method m; 91 | t_max_err err; 92 | long argc = 0; 93 | t_atom *argv = NULL; 94 | 95 | if (mess && mess->s_name) { 96 | err = atom_setparse(&argc, &argv, mess->s_name); 97 | if (!err) { 98 | mess = atom_getsym(argv); 99 | if (mess == gensym("/ping")) { 100 | testport_send(u, gensym("/ping/return"), 0, NULL); 101 | } 102 | else if (mess == gensym("/db/ready?")) { 103 | if (ps_db_ready->s_thing) { 104 | testport_send(u, gensym("/db/ready"), 0, NULL); 105 | } 106 | } 107 | else if (mess == gensym("/testdb/path?")) { 108 | t_atom a; 109 | char ret[MAX_PATH_CHARS]; 110 | 111 | atom_setsym(&a, gensym(g_dbpath)); 112 | snprintf_zero(ret, MAX_PATH_CHARS, "/testdb/path %s", g_dbpath); 113 | testport_send(u, gensym("/testdb/path"), 1, &a); 114 | } 115 | else if (mess && mess->s_thing && !NOGOOD(mess->s_thing) && argc && argv) { 116 | m = zgetfn(mess->s_thing, atom_getsym(argv+1)); 117 | 118 | // messages may come in packed as a single string, or a string with args 119 | // in either case, the first symbol is the name of an object, whose s_thing should be the destination 120 | // the second symbol is the name of the method 121 | // any additional arguments are, naturally, arguments 122 | 123 | // udp callback is on a non-main thread, so we use defer_low() to get back onto the main thread 124 | if (m) 125 | defer_low(mess->s_thing, m, atom_getsym(argv+1), (short)argc-2, argv+2); 126 | else if (so->flags && so->thing) { 127 | long ac = so->flags; 128 | t_atom *av = (t_atom*)so->thing; 129 | 130 | m = zgetfn(mess->s_thing, atom_getsym(av)); 131 | // udp callback is on a non-main thread, so we use defer_low() to get back onto the main thread 132 | if (m) 133 | defer_low(mess->s_thing, m, atom_getsym(av), (short)ac-1, av+1); 134 | } 135 | } 136 | sysmem_freeptr(argv); 137 | } 138 | } 139 | } 140 | else if (msg == _sym_free) { 141 | object_detach_byptr(u, u->u_udpreceive); 142 | u->u_udpreceive = NULL; 143 | } 144 | } 145 | return MAX_ERR_NONE; 146 | } 147 | 148 | 149 | t_max_err testport_send(t_testport *u, t_symbol *msg, long argc, t_atom *argv) 150 | { 151 | if (u->u_udpsend) 152 | return object_method_typed(u->u_udpsend, msg, argc, argv, NULL); 153 | else 154 | return MAX_ERR_GENERIC; 155 | } 156 | 157 | 158 | void testport_ping(t_testport *u) 159 | { 160 | testport_send(u, gensym("/ping/return"), 0, NULL); 161 | } 162 | -------------------------------------------------------------------------------- /source/projects/oscar/test.runner.c: -------------------------------------------------------------------------------- 1 | // 2 | // unit test extension for max 3 | // tim place 4 | // cycling '74 5 | // 6 | 7 | #include "oscar.h" 8 | #include "jpatcher_api.h" 9 | t_object *jwind_gettopwindow(void); 10 | 11 | 12 | // class variables 13 | static t_class *s_testrunner_class = NULL; 14 | static t_symbol *ps_test_terminate = NULL; 15 | 16 | 17 | /************************************************************************/ 18 | 19 | void testrunner_classinit(void) 20 | { 21 | t_class *c = class_new("test.runner", (method)testrunner_new, (method)testrunner_free, sizeof(t_testrunner), (method)NULL, A_GIMME, 0L); 22 | 23 | class_addmethod(c, (method)testrunner_integration, "integration", 0); // integration tests 24 | class_addmethod(c, (method)testrunner_notify, "notify", A_CANT, 0); // notifications from integration tests 25 | 26 | CLASS_ATTR_SYM_VARSIZE(c, "tests", 0, t_testrunner, r_testnames, r_numtestnames, 256); 27 | 28 | class_register(_sym_nobox, c); 29 | s_testrunner_class = c; 30 | 31 | ps_test_terminate = gensym("test.terminate"); 32 | } 33 | 34 | 35 | void* testrunner_new(t_symbol *name, long argc, t_atom *argv) 36 | { 37 | t_testrunner *r = (t_testrunner*)object_alloc(s_testrunner_class); 38 | 39 | if (r) { 40 | r->r_qelem = (t_qelem*)qelem_new(r, (method)testrunner_one_integration); 41 | r->r_qelem_iter = (t_qelem*)qelem_new(r, (method)testrunner_dointegration); 42 | attr_args_process(r, (short)argc, argv); 43 | } 44 | return r; 45 | } 46 | 47 | 48 | void testrunner_free(t_testrunner *r) 49 | { 50 | qelem_free(r->r_qelem); 51 | qelem_free(r->r_qelem_iter); 52 | } 53 | 54 | 55 | void testrunner_notify(t_testrunner *r, t_symbol *s, t_symbol *msg, void *sender, void *data) 56 | { 57 | if (sender == r->r_testunit && msg == gensym("terminate")) { 58 | r->r_terminated = true; 59 | } 60 | } 61 | 62 | 63 | void testrunner_one_integration(t_testrunner *r, t_symbol *testname) 64 | { 65 | t_testdb *db = (t_testdb*)((t_testmaster*)(ps_testmaster->s_thing))->m_db; 66 | t_symbol *pound_t = gensym("#T"); 67 | 68 | if (!r->r_running) { // start it 69 | t_testunit *testunit = (t_testunit*)testunit_new(NULL, 0, NULL); 70 | 71 | r->r_testid = testdb_createcase(db, testname->s_name); 72 | testunit->o_db = db; 73 | testunit->o_id = r->r_testid; 74 | testdb_log(db, r->r_testid, "preparing to run %s", testname->s_name); 75 | 76 | pound_t->s_thing = (t_object*)testunit; 77 | object_method(_sym_max->s_thing, _sym_openfile, symbol_unique(), testname, 0); 78 | 79 | object_attach_byptr_register(r, testunit, _sym_nobox); 80 | r->r_testunit = (t_test*)testunit; 81 | r->r_running = true; 82 | } 83 | else { // try to finish it 84 | if (r->r_terminated) { 85 | t_max_err err = MAX_ERR_NONE; 86 | 87 | // moved to here from the block above because when loading a maxzip the patcher instantiation maybe deferred/asynchronous 88 | pound_t->s_thing = NULL; 89 | 90 | testdb_log(db, r->r_testid, "concluding test"); 91 | 92 | err = object_free(r->r_testunit); 93 | 94 | testdb_log(db, r->r_testid, "test patcher freed with status %i", err); 95 | testdb_closecase(db, r->r_testid); 96 | 97 | r->r_running = false; 98 | return; 99 | } 100 | } 101 | qelem_set(r->r_qelem); 102 | } 103 | 104 | 105 | static int s_current_test_index; 106 | 107 | void testrunner_integration(t_testrunner *r) 108 | { 109 | int i; 110 | 111 | for (i=0; i < r->r_numtestnames ;i++) 112 | testrunner_one_integration(r, r->r_testnames[i]); 113 | } 114 | 115 | 116 | void testrunner_dointegration(t_testrunner *r) 117 | { 118 | if (r->r_running) 119 | ; // need to wait until the current test is done before starting the next one... 120 | else { 121 | if (s_current_test_index >= r->r_numtestnames) 122 | return; 123 | 124 | testrunner_one_integration(r, r->r_testnames[s_current_test_index]); 125 | s_current_test_index++; 126 | } 127 | qelem_set(r->r_qelem_iter); 128 | } 129 | 130 | 131 | -------------------------------------------------------------------------------- /source/projects/oscar/test.sample~.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // unit/integration test extension for max 3 | // tim place 4 | // cycling '74 5 | // 6 | 7 | #include "oscar.h" 8 | #include "z_dsp.h" 9 | 10 | #define MAXSAMPLECOUNT 64 11 | 12 | 13 | /** like snapshot~ but a little easier to get predictable results in the test context. 14 | for example, snapshot~ will return a result when banged even if dsp has never finished executing a single vector. 15 | */ 16 | typedef struct _testsample { 17 | t_pxobject x_ob; ///< header 18 | t_object *x_patcher; ///< the patcher in which the object exists -- assumed to be a top-level patcher 19 | t_test *x_test; ///< test object that is calling this assertion 20 | void *x_outlet; ///< float/list for sampled values 21 | t_clock *x_clock; ///< clock for pushing the data onto the scheduler from the audio thread 22 | long x_offset; ///< attr: offset into the vector at which to grab the sample 23 | long x_vectoroffset; ///< how many vectors to wait before grabbing a sample 24 | long x_vectorcountdown; ///< the counter of how many vectors remain to pass by before grabbing a sample 25 | long x_samplecount; ///< attr: number of samples to grab 26 | double x_samples[MAXSAMPLECOUNT]; ///< samples to return 27 | long x_hasrun; ///< have we run our perform method and returned output yet? 28 | long x_autorun; ///> run automatically? default is yes 29 | } t_testsample; 30 | 31 | 32 | // prototypes 33 | void* testsample_new(t_symbol *s, long argc, t_atom *argv); 34 | void testsample_free(t_testsample *x); 35 | void testsample_assist(t_testsample *x, void *b, long m, long a, char *s); 36 | void testsample_bang(t_testsample *x); 37 | void testsample_tick(t_testsample *x); 38 | void testsample_dsp64(t_testsample *x, t_object *dsp64, short *count, double samplerate, long maxvectorsize, long flags); 39 | 40 | 41 | // class variables 42 | static t_class *s_testsample_class = NULL; 43 | 44 | 45 | /************************************************************************/ 46 | 47 | void testsample_classinit(void) 48 | { 49 | t_class *c = class_new("test.sample~", 50 | (method)testsample_new, 51 | (method)testsample_free, 52 | sizeof(t_testsample), 53 | (method)NULL, 54 | A_GIMME, 55 | 0L); 56 | 57 | class_addmethod(c, (method)testsample_bang, "bang", 0); 58 | class_addmethod(c, (method)testsample_dsp64, "dsp64", A_CANT, 0); 59 | class_addmethod(c, (method)testsample_assist, "assist", A_CANT, 0); 60 | 61 | CLASS_ATTR_LONG(c, "offset", 0, t_testsample, x_offset); 62 | CLASS_ATTR_LONG(c, "vectoroffset", 0, t_testsample, x_vectoroffset); 63 | CLASS_ATTR_LONG(c, "count", 0, t_testsample, x_samplecount); 64 | CLASS_ATTR_LONG(c, "autorun", 0, t_testsample, x_autorun); 65 | 66 | class_dspinit(c); 67 | class_register(_sym_box, c); 68 | s_testsample_class = c; 69 | } 70 | 71 | 72 | /************************************************************************/ 73 | 74 | 75 | void* testsample_new(t_symbol *s, long argc, t_atom *argv) 76 | { 77 | t_testsample *x = (t_testsample*)object_alloc(s_testsample_class); 78 | 79 | if (x) { 80 | dsp_setup((t_pxobject *)x, 1); 81 | x->x_clock = (t_clock*)clock_new(x, (method)testsample_tick); 82 | x->x_outlet = outlet_new(x, NULL); 83 | x->x_test = (t_test*)gensym("#T")->s_thing; 84 | x->x_samplecount = 1; 85 | x->x_autorun = true; 86 | attr_args_process(x, (short)argc, argv); 87 | x->x_vectorcountdown = x->x_vectoroffset; 88 | if (!x->x_autorun) 89 | x->x_hasrun = true; // if we aren't going to automatically fire, then pretend we already did it 90 | } 91 | autocolorbox((t_object*)x); 92 | return x; 93 | } 94 | 95 | 96 | void testsample_free(t_testsample *x) 97 | { 98 | dsp_free((t_pxobject*)x); 99 | clock_free(x->x_clock); 100 | } 101 | 102 | 103 | #pragma mark - 104 | /************************************************************************/ 105 | 106 | void testsample_assist(t_testsample *x, void *b, long m, long a, char *s) 107 | { 108 | strcpy(s, "log messages to the test result, or to the max window"); 109 | } 110 | 111 | 112 | void testsample_tick(t_testsample *x) 113 | { 114 | if (x->x_samplecount == 1) 115 | outlet_float(x->x_outlet, x->x_samples[0]); 116 | else { 117 | t_atom a[MAXSAMPLECOUNT]; 118 | int i; 119 | 120 | for (i=0; i < x->x_samplecount; i++) 121 | atom_setfloat(a+i, x->x_samples[i]); 122 | outlet_anything(x->x_outlet, _sym_list, (short)x->x_samplecount, a); 123 | } 124 | } 125 | 126 | 127 | void testsample_bang(t_testsample *x) 128 | { 129 | x->x_vectorcountdown = x->x_vectoroffset; 130 | x->x_hasrun = false; 131 | } 132 | 133 | 134 | void testsample_perform64(t_testsample *x, t_object *dsp64, double **ins, long numins, double **outs, long numouts, long sampleframes, long flags, void *userparam) 135 | { 136 | if (x->x_vectorcountdown) { 137 | x->x_vectorcountdown--; 138 | return; 139 | } 140 | if (!x->x_hasrun) { 141 | memcpy(x->x_samples, ins[0]+x->x_offset, sizeof(t_double) * x->x_samplecount); 142 | clock_delay(x->x_clock, 0); 143 | x->x_hasrun = true; 144 | } 145 | } 146 | 147 | 148 | void testsample_dsp64(t_testsample *x, t_object *dsp64, short *count, double samplerate, long maxvectorsize, long flags) 149 | { 150 | dsp_add64(dsp64, (t_object*)x, (t_perfroutine64)testsample_perform64, 0, NULL); 151 | } 152 | 153 | -------------------------------------------------------------------------------- /source/projects/oscar/test.terminate.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // unit/integration test extension for max 3 | // tim place 4 | // cycling '74 5 | // 6 | 7 | #include "oscar.h" 8 | 9 | 10 | // class variables 11 | static t_class *s_testterminate_class = NULL; 12 | extern t_linklist *g_all_assert_instances; 13 | 14 | 15 | /************************************************************************/ 16 | 17 | void testterminate_classinit(void) 18 | { 19 | t_class *c = class_new("test.terminate", 20 | (method)testterminate_new, 21 | (method)testterminate_free, 22 | sizeof(t_testterminate), 23 | (method)NULL, 24 | A_GIMME, 25 | 0L); 26 | 27 | class_addmethod(c, (method)testterminate_bang, "bang", 0); 28 | class_addmethod(c, (method)testterminate_assist, "assist", A_CANT, 0); 29 | 30 | class_register(_sym_box, c); 31 | s_testterminate_class = c; 32 | } 33 | 34 | 35 | /************************************************************************/ 36 | 37 | void* testterminate_new(t_symbol *s, long argc, t_atom *argv) 38 | { 39 | t_testterminate *x = (t_testterminate*)object_alloc(s_testterminate_class); 40 | 41 | if (x) { 42 | object_obex_lookup(x, _sym_pound_P, &x->x_patcher); 43 | x->x_test = (t_test*)gensym("#T")->s_thing; 44 | attr_args_process(x, (short)argc, argv); 45 | } 46 | autocolorbox((t_object*)x); 47 | return x; 48 | } 49 | 50 | 51 | void testterminate_free(t_testterminate *x) 52 | { 53 | // Notify the test harness that we are done so it can start another test if desired 54 | if (x->x_test && !NOGOOD(x->x_test)) 55 | object_method(x->x_test, gensym("terminate")); 56 | } 57 | 58 | 59 | #pragma mark - 60 | /************************************************************************/ 61 | 62 | 63 | void testterminate_qfn(t_testterminate *x) 64 | { 65 | if (x->x_test) { 66 | // 1. Iterate through all the assert instances and tell them to log their results 67 | linklist_methodall(g_all_assert_instances, gensym("pop")); 68 | 69 | // 2. Close the patcher 70 | object_method(x->x_patcher, _sym_wclose); 71 | } 72 | } 73 | 74 | 75 | void testterminate_dobang(t_testterminate *x) 76 | { 77 | object_method(_sym_dsp->s_thing, _sym_stop); 78 | defer_low(x, (method)testterminate_qfn, NULL, 0, NULL); 79 | } 80 | 81 | 82 | void testterminate_bang(t_testterminate *x) 83 | { 84 | defer_low(x, (method)testterminate_dobang, NULL, 0, NULL); 85 | } 86 | 87 | 88 | void testterminate_assist(t_testterminate *x, void *b, long m, long a, char *s) 89 | { 90 | strcpy(s, "bang to signal the test to terminate"); 91 | } 92 | 93 | -------------------------------------------------------------------------------- /source/projects/oscar/test.unit.c: -------------------------------------------------------------------------------- 1 | // 2 | // unit test extension for max 3 | // tim place 4 | // cycling '74 5 | // 6 | 7 | #include "oscar.h" 8 | 9 | 10 | // class variables 11 | static t_class *s_testunit_class = NULL; 12 | 13 | 14 | /************************************************************************/ 15 | 16 | void testunit_classinit(void) 17 | { 18 | t_class *c = class_new("test.unit", 19 | (method)testunit_new, 20 | (method)testunit_free, 21 | sizeof(t_testunit), 22 | (method)NULL, 23 | A_GIMME, 24 | 0L); 25 | 26 | class_addmethod(c, (method)testunit_log, "log", A_CANT, 0); 27 | class_addmethod(c, (method)testunit_assert, "assert", A_CANT, 0); 28 | class_addmethod(c, (method)testunit_terminate, "terminate", A_CANT, 0); 29 | 30 | class_register(_sym_nobox, c); 31 | s_testunit_class = c; 32 | } 33 | 34 | 35 | /************************************************************************/ 36 | 37 | void* testunit_new(t_symbol *s, long argc, t_atom *argv) 38 | { 39 | t_testunit *u = (t_testunit*)object_alloc(s_testunit_class); 40 | 41 | if (u) { 42 | attr_args_process(u, (short)argc, argv); 43 | } 44 | return u; 45 | } 46 | 47 | 48 | void testunit_free(t_testunit *u) 49 | { 50 | ; 51 | } 52 | 53 | 54 | void testunit_log(t_testunit *u, const char* text) 55 | { 56 | testdb_log(u->o_db, u->o_id, text); 57 | } 58 | 59 | 60 | void testunit_assert(t_testunit *u, const char* assertion_name, t_bool passed, t_symbol **tags, long tag_count) 61 | { 62 | t_ptr_uint timestamp = systime_seconds(); 63 | t_datetime datetime; 64 | char ctags[4096]; 65 | long i; 66 | 67 | ctags[0] = 0; 68 | for (i=0; is_name, 4096); 71 | else { 72 | strncat_zero(ctags, " ", 4096); 73 | strncat_zero(ctags, tags[i]->s_name, 4096); 74 | } 75 | } 76 | 77 | systime_secondstodate(timestamp, &datetime); 78 | 79 | db_query(u->o_db->d_db, NULL, "INSERT INTO assertions ( test_id_ext , assertion_name , assertion_finish, assertion_value, assertion_tags ) \ 80 | VALUES ( %i , \"%s\" , '%4u-%02u-%02u %02u:%02u:%02u', \'%s\', \"%s\" )", 81 | u->o_id, assertion_name, 82 | datetime.year, datetime.month, datetime.day, datetime.hour, datetime.minute, datetime.second, 83 | (passed?"Pass":"Fail"), 84 | ctags 85 | ); 86 | } 87 | 88 | 89 | void testunit_terminate(t_testunit *u) 90 | { 91 | object_notify(u, gensym("terminate"), NULL); 92 | } 93 | 94 | -------------------------------------------------------------------------------- /zip-it.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | RELEASE=v1.1-beta1 4 | 5 | (cd .. \ 6 | && rm -f max-test-$RELEASE.zip \ 7 | && zip -r max-test-$RELEASE.zip max-test -x 'max-test/source/maxsdk/*' 'max-test/.git/*') 8 | --------------------------------------------------------------------------------