├── .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 |
--------------------------------------------------------------------------------