To check out this repository please hg clone the following URL, or open the URL using EasyMercurial or your preferred Mercurial client.
root
| 1 | 68:b75124ebe697 | Chris | |
|---|---|---|---|
| 2 | configuration: |
||
| 3 | - Release |
||
| 4 | |||
| 5 | platform: |
||
| 6 | - x86 |
||
| 7 | - x64 |
||
| 8 | |||
| 9 | 70:2d3e1d1f99c0 | Chris | install: |
| 10 | - cinst --allow-empty-checksums smlnj |
||
| 11 | - ps: '"[hostfingerprints]" | Out-File -Encoding "ASCII" -Append $env:USERPROFILE\mercurial.ini' |
||
| 12 | 80:f84e12a1553c | Chris | - ps: '"code.soundsoftware.ac.uk = C7:27:7E:2C:1E:67:62:90:E9:8D:27:52:66:30:E6:FE:D4:2C:C5:30" | Out-File -Encoding "ASCII" -Append $env:USERPROFILE\mercurial.ini' |
| 13 | 70:2d3e1d1f99c0 | Chris | - ps: '"[hostsecurity]" | Out-File -Encoding "ASCII" -Append $env:USERPROFILE\mercurial.ini' |
| 14 | 80:f84e12a1553c | Chris | - ps: '"code.soundsoftware.ac.uk = code.soundsoftware.ac.uk:fingerprints=sha256:AF:67:D8:D6:D8:2F:28:FF:B9:1A:4D:CD:6A:93:25:EC:9E:47:9E:0E:E7:F1:FD:0B:97:B4:7C:D5:FA:2E:10:73" | Out-File -Encoding "ASCII" -Append $env:USERPROFILE\mercurial.ini' |
| 15 | 70:2d3e1d1f99c0 | Chris | |
| 16 | before_build: |
||
| 17 | - set PATH=%PATH%;C:\Program Files (x86)\SMLNJ\bin |
||
| 18 | 78:ae8b4cafb6bf | Chris | - repoint install |
| 19 | 70:2d3e1d1f99c0 | Chris | |
| 20 | 68:b75124ebe697 | Chris | build: |
| 21 | project: build\vamp-plugin-tester.sln |
||
| 22 | |||
| 23 | 74:42cc6dfc0026 | Chris | artifacts: |
| 24 | 75:8fd5075ed6d4 | Chris | - path: '**\*.exe' |
| 25 | 74:42cc6dfc0026 | Chris | |
| 26 | 46:d83ac7c87f9c | Chris | syntax: glob |
| 27 | *.o |
||
| 28 | *~ |
||
| 29 | *.so |
||
| 30 | *.dll |
||
| 31 | *.exe |
||
| 32 | vamp-plugin-tester |
||
| 33 | 67:fa66ee7dcf08 | Chris | build/Release |
| 34 | build/x64/Release |
||
| 35 | build/Debug |
||
| 36 | build/x64/Debug |
||
| 37 | *.VC.db |
||
| 38 | *.VC.opendb |
||
| 39 | *.filters |
||
| 40 | 76:a6c9a0ca493e | Chris | glob:.repoint-*.bin |
| 41 | 29:4534c910639c | convert-repo | b1bc4d045a4be1dc802043d90c54538454fcb2d9 vamp-plugin-tester-v1.0 |
| 42 | eff1772ba3974c163681387c1455bcc9aa853e1f vamp-plugin-tester-v1.0-erroneous |
||
| 43 | 64:a1e590fa9fbb | Chris | d19bbfc055abb6c7733f9f808b2bd1cd6dfa80c9 vamp-plugin-tester-v1.1 |
| 44 | 59:6732dd5e7139 | Chris | |
| 45 | Changes in Vamp Plugin Tester v1.1 since the previous release v1.0: |
||
| 46 | |||
| 47 | Features |
||
| 48 | |||
| 49 | * Add mechanism for listing the available tests and running only a |
||
| 50 | single test at a time |
||
| 51 | 73:2958013ed16a | Chris | |
| 52 | 59:6732dd5e7139 | Chris | * Add test for reset() changing effective parameter values |
| 53 | 65:454a1cb1b506 | Chris | |
| 54 | * Add test for fixed-sample-rate output defined without sample rate |
||
| 55 | 73:2958013ed16a | Chris | |
| 56 | 59:6732dd5e7139 | Chris | * Add test for existence of .cat file |
| 57 | |||
| 58 | * Add test for boilerplate description text still in place for VamPy |
||
| 59 | plugins |
||
| 60 | 73:2958013ed16a | Chris | |
| 61 | 59:6732dd5e7139 | Chris | * Make output more informative for some tests, and make some tests faster |
| 62 | |||
| 63 | Bug fixes |
||
| 64 | |||
| 65 | * Fix erroneous verbose timestamp output for some tests |
||
| 66 | |||
| 67 | * Don't complain when two consecutive runs with different starting |
||
| 68 | times produce the same results, if neither set of results has any |
||
| 69 | timestamps anyway |
||
| 70 | |||
| 71 | * Link against pthread library on Linux, to support plugins with |
||
| 72 | threading |
||
| 73 | 18:809fcc3d7f4e | cannam | |
| 74 | Permission is hereby granted, free of charge, to any person |
||
| 75 | obtaining a copy of this software and associated documentation |
||
| 76 | files (the "Software"), to deal in the Software without |
||
| 77 | restriction, including without limitation the rights to use, copy, |
||
| 78 | modify, merge, publish, distribute, sublicense, and/or sell copies |
||
| 79 | of the Software, and to permit persons to whom the Software is |
||
| 80 | furnished to do so, subject to the following conditions: |
||
| 81 | |||
| 82 | The above copyright notice and this permission notice shall be |
||
| 83 | included in all copies or substantial portions of the Software. |
||
| 84 | |||
| 85 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
||
| 86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
||
| 87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
||
| 88 | NONINFRINGEMENT. IN NO EVENT SHALL THE X CONSORTIUM BE LIABLE FOR |
||
| 89 | ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF |
||
| 90 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
||
| 91 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
||
| 92 | |||
| 93 | Except as contained in this notice, the names of the Centre for |
||
| 94 | Digital Music; Queen Mary, University of London; and Chris Cannam |
||
| 95 | shall not be used in advertising or otherwise to promote the sale, |
||
| 96 | use or other dealings in this Software without prior written |
||
| 97 | authorization. |
||
| 98 | |||
| 99 | 0:f89128a316e7 | cannam | |
| 100 | 48:0a44d3e43830 | Chris | ARCHFLAGS ?= |
| 101 | |||
| 102 | 57:fc05ac9b3947 | Chris | LDFLAGS += $(ARCHFLAGS) -ldl -pthread |
| 103 | 69:eebbadf1c959 | Chris | CXXFLAGS += $(ARCHFLAGS) -std=c++98 -g -Wall -Wextra -Ivamp-plugin-sdk -pthread |
| 104 | 0:f89128a316e7 | cannam | |
| 105 | 51:13db8d010367 | Chris | # We include the Vamp Host SDK sources in the build here, so that we |
| 106 | # can build the entire thing with debug symbols even though the SDK |
||
| 107 | # would not normally have them |
||
| 108 | 0:f89128a316e7 | cannam | |
| 109 | 51:13db8d010367 | Chris | VAMP_SRCDIR := vamp-plugin-sdk/src/vamp-hostsdk |
| 110 | |||
| 111 | VAMP_OBJECTS := \ |
||
| 112 | $(VAMP_SRCDIR)/PluginHostAdapter.o \ |
||
| 113 | $(VAMP_SRCDIR)/RealTime.o \ |
||
| 114 | $(VAMP_SRCDIR)/PluginBufferingAdapter.o \ |
||
| 115 | $(VAMP_SRCDIR)/PluginChannelAdapter.o \ |
||
| 116 | $(VAMP_SRCDIR)/PluginInputDomainAdapter.o \ |
||
| 117 | $(VAMP_SRCDIR)/PluginLoader.o \ |
||
| 118 | $(VAMP_SRCDIR)/PluginSummarisingAdapter.o \ |
||
| 119 | $(VAMP_SRCDIR)/PluginWrapper.o \ |
||
| 120 | 55:436a14a12242 | Chris | $(VAMP_SRCDIR)/Files.o \ |
| 121 | 51:13db8d010367 | Chris | $(VAMP_SRCDIR)/acsymbols.o |
| 122 | |||
| 123 | OBJECTS := \ |
||
| 124 | $(VAMP_OBJECTS) \ |
||
| 125 | vamp-plugin-tester.o \ |
||
| 126 | Tester.o \ |
||
| 127 | Test.o \ |
||
| 128 | TestStaticData.o \ |
||
| 129 | TestInputExtremes.o \ |
||
| 130 | TestMultipleRuns.o \ |
||
| 131 | TestOutputs.o \ |
||
| 132 | TestDefaults.o \ |
||
| 133 | TestInitialise.o |
||
| 134 | |||
| 135 | 70:2d3e1d1f99c0 | Chris | vamp-plugin-tester: vamp-plugin-sdk/README $(OBJECTS) $(VAMP_OBJECTS) |
| 136 | 22:ca6803a93bb7 | cannam | $(CXX) $(OBJECTS) -o $@ $(LDFLAGS) |
| 137 | 0:f89128a316e7 | cannam | |
| 138 | 70:2d3e1d1f99c0 | Chris | vamp-plugin-sdk/README: |
| 139 | 76:a6c9a0ca493e | Chris | ./repoint install |
| 140 | 70:2d3e1d1f99c0 | Chris | |
| 141 | 0:f89128a316e7 | cannam | clean: |
| 142 | 51:13db8d010367 | Chris | rm -f $(OBJECTS) $(VAMP_OBJECTS) |
| 143 | 0:f89128a316e7 | cannam | |
| 144 | distclean: clean |
||
| 145 | rm -f *~ vamp-plugin-tester |
||
| 146 | |||
| 147 | depend: |
||
| 148 | 51:13db8d010367 | Chris | makedepend -Y *.cpp *.h $(VAMP_SRCDIR)/*.cpp |
| 149 | 0:f89128a316e7 | cannam | |
| 150 | # DO NOT DELETE |
||
| 151 | |||
| 152 | Test.o: Test.h |
||
| 153 | 10:0c1c60654125 | cannam | TestDefaults.o: TestDefaults.h Test.h Tester.h |
| 154 | 51:13db8d010367 | Chris | Tester.o: Tester.h Test.h |
| 155 | 14:e48fdc8de790 | cannam | TestInitialise.o: TestInitialise.h Test.h Tester.h |
| 156 | 3:0f65bb22172b | cannam | TestInputExtremes.o: TestInputExtremes.h Test.h Tester.h |
| 157 | TestMultipleRuns.o: TestMultipleRuns.h Test.h Tester.h |
||
| 158 | TestOutputs.o: TestOutputs.h Test.h Tester.h |
||
| 159 | 0:f89128a316e7 | cannam | TestStaticData.o: TestStaticData.h Test.h Tester.h |
| 160 | vamp-plugin-tester.o: Tester.h Test.h |
||
| 161 | 10:0c1c60654125 | cannam | TestDefaults.o: Test.h Tester.h |
| 162 | 51:13db8d010367 | Chris | Tester.o: Test.h |
| 163 | 10:0c1c60654125 | cannam | TestInitialise.o: Test.h Tester.h |
| 164 | 3:0f65bb22172b | cannam | TestInputExtremes.o: Test.h Tester.h |
| 165 | TestMultipleRuns.o: Test.h Tester.h |
||
| 166 | TestOutputs.o: Test.h Tester.h |
||
| 167 | 0:f89128a316e7 | cannam | TestStaticData.o: Test.h Tester.h |
| 168 | 51:13db8d010367 | Chris | vamp-plugin-sdk/src/vamp-hostsdk/PluginInputDomainAdapter.o: vamp-plugin-sdk/src/vamp-hostsdk/Window.h |
| 169 | vamp-plugin-sdk/src/vamp-hostsdk/PluginInputDomainAdapter.o: vamp-plugin-sdk/src/vamp-sdk/FFTimpl.cpp |
||
| 170 | vamp-plugin-sdk/src/vamp-hostsdk/RealTime.o: vamp-plugin-sdk/src/vamp-sdk/RealTime.cpp |
||
| 171 | 17:ea8865f488a0 | cannam | |
| 172 | Vamp Plugin Tester |
||
| 173 | ================== |
||
| 174 | |||
| 175 | This program tests Vamp audio feature extraction plugins |
||
| 176 | (http://vamp-plugins.org/) for certain common failure cases. |
||
| 177 | |||
| 178 | To test a single plugin, run vamp-plugin-tester with the name of your |
||
| 179 | plugin library and plugin identifier, separated by a colon. For example, |
||
| 180 | |||
| 181 | $ vamp-plugin-tester vamp-example-plugins:amplitudefollower |
||
| 182 | |||
| 183 | The plugin library must be installed in the Vamp plugin path (you |
||
| 184 | cannot give the path to the library file). |
||
| 185 | |||
| 186 | |||
| 187 | Options |
||
| 188 | ======= |
||
| 189 | |||
| 190 | 27:5dcdc86d45d4 | cannam | Supply the -a or --all option to tell vamp-plugin-tester to test all |
| 191 | plugins found in your Vamp path. |
||
| 192 | |||
| 193 | 17:ea8865f488a0 | cannam | Supply the -v or --verbose option to tell vamp-plugin-tester to print |
| 194 | out the whole content of its returned feature log for diagnostic |
||
| 195 | purposes each time it prints an error or warning that arises from the |
||
| 196 | contents of a returned feature. |
||
| 197 | |||
| 198 | Supply the -n or --nondeterministic option to tell vamp-plugin-tester |
||
| 199 | that your plugins are expected to return different results each time |
||
| 200 | they are run. The default behaviour is to treat different results on |
||
| 201 | separate runs with the same input data as an error. |
||
| 202 | |||
| 203 | 41:4d04c4fa1905 | Chris | Supply the -t or --test option with a test ID argument to tell |
| 204 | vamp-plugin-tester to run only a single test, rather than the complete |
||
| 205 | test suite. To find out what test ID to use for a given test, run |
||
| 206 | vamp-plugin-tester with the --list-tests or -l option. |
||
| 207 | |||
| 208 | 17:ea8865f488a0 | cannam | |
| 209 | Errors and Warnings |
||
| 210 | =================== |
||
| 211 | |||
| 212 | Each test may cause one or several notes, warnings, or errors to be |
||
| 213 | printed. A note is printed when behaviour is observed that may be |
||
| 214 | correct behaviour but that is not always anticipated by the plugin |
||
| 215 | developer. A warning is printed when behaviour is observed that is |
||
| 216 | technically legal but that in practice most often happens by mistake. |
||
| 217 | An error is printed when behaviour is observed that cannot be correct. |
||
| 218 | |||
| 219 | vamp-plugin-tester prints all of its commentary to the standard |
||
| 220 | output. Standard error is usually used for diagnostic output printed |
||
| 221 | by the plugins themselves. |
||
| 222 | |||
| 223 | 19:34d52412039c | cannam | In addition to reports, vamp-plugin-tester runs some tests that are |
| 224 | intended to provoke the plugin into unexpected behaviour such as |
||
| 225 | memory errors. If vamp-plugin-tester crashes during a test, this may |
||
| 226 | be why. If you have access to a memory checker utility such as |
||
| 227 | valgrind, you are advised to run vamp-plugin-tester under it so as to |
||
| 228 | be informed of any memory errors that do not happen to cause crashes |
||
| 229 | (as well as memory leaks). The vamp-plugin-tester binaries |
||
| 230 | distributed by QMUL have been compiled with debug information |
||
| 231 | included, in order to facilitate this type of use. |
||
| 232 | 17:ea8865f488a0 | cannam | |
| 233 | |||
| 234 | Error and Warning Reference |
||
| 235 | =========================== |
||
| 236 | |||
| 237 | ** ERROR: Failed to load plugin |
||
| 238 | |||
| 239 | The plugin could not be loaded. Remember that the plugin must be |
||
| 240 | installed in the Vamp plugin path. |
||
| 241 | |||
| 242 | 24:064ad81ea53c | cannam | Normally this message will be preceded by one of the following |
| 243 | errors: |
||
| 244 | |||
| 245 | 26:eff1772ba397 | cannam | Invalid plugin key <key> in loadPlugin |
| 246 | 24:064ad81ea53c | cannam | |
| 247 | 26:eff1772ba397 | cannam | - The argument given to vamp-plugin-tester could not be split |
| 248 | into library name and plugin identifier. Check the usage |
||
| 249 | description above. |
||
| 250 | |||
| 251 | No library found in Vamp path for plugin <key> |
||
| 252 | |||
| 253 | - No Vamp plugin library of that name was found in the Vamp path. |
||
| 254 | This message will often be accompanied by one of the following |
||
| 255 | errors; if it isn't, then that probably means the file did not |
||
| 256 | exist at all. |
||
| 257 | 24:064ad81ea53c | cannam | |
| 258 | Plugin <id> not found in library <name> |
||
| 259 | |||
| 260 | - The library was found and loaded and was apparently a valid |
||
| 261 | Vamp plugin library, but it didn't contain a plugin of that id. |
||
| 262 | Check you typed the id correctly, and if this is your library, |
||
| 263 | check that the vampGetPluginDescriptor function returns the |
||
| 264 | plugin descriptor properly. |
||
| 265 | |||
| 266 | Unable to load library <name> |
||
| 267 | |||
| 268 | - A dynamic library of that name was found, but the system library |
||
| 269 | loader could not load it. Perhaps it depends on another library |
||
| 270 | 26:eff1772ba397 | cannam | that is not available, or it was built for the wrong architecture. |
| 271 | There may be more information in the error message. |
||
| 272 | 24:064ad81ea53c | cannam | |
| 273 | No vampGetPluginDescriptor function found in library <name> |
||
| 274 | |||
| 275 | - A dynamic library of that name was found and loaded, but it |
||
| 276 | lacked the necessary public vampGetPluginDescriptor function. |
||
| 277 | |||
| 278 | * Are you sure this is a Vamp plugin library? |
||
| 279 | * If you made it, did you remember to include the global |
||
| 280 | vampGetPluginDescriptor function in your library along with |
||
| 281 | your plugin classes? |
||
| 282 | * If you are using Visual C++, did you remember to mark the |
||
| 283 | vampGetPluginDescriptor symbol exported, as described in |
||
| 284 | the README.msvc file in the SDK? |
||
| 285 | |||
| 286 | 17:ea8865f488a0 | cannam | ** ERROR: (plugin|parameter|output) identifier <x> contains invalid characters |
| 287 | |||
| 288 | An identifier contains characters other than the permitted set (ASCII |
||
| 289 | lower and upper case letters, digits, "-" and "_" only). |
||
| 290 | |||
| 291 | ** ERROR: <field> is empty |
||
| 292 | |||
| 293 | A mandatory field, such as the name of a parameter or output, |
||
| 294 | contains no text. |
||
| 295 | |||
| 296 | ** WARNING: <field> is empty |
||
| 297 | |||
| 298 | An optional field, such as the description of a parameter or output, |
||
| 299 | contains no text. |
||
| 300 | |||
| 301 | ** ERROR: Plugin parameter <x> maxValue <= minValue |
||
| 302 | |||
| 303 | The minimum and maximum values given for a parameter are equal or in |
||
| 304 | the wrong order. |
||
| 305 | |||
| 306 | ** ERROR: Plugin parameter <x> defaultValue out of range |
||
| 307 | |||
| 308 | The default value for a parameter is not within the range defined by |
||
| 309 | the minimum and maximum values for the parameter. |
||
| 310 | |||
| 311 | ** ERROR: Plugin parameter <x> is quantized, but quantize step is zero |
||
| 312 | |||
| 313 | The quantizeStep value in a parameter with isQuantized true is set to |
||
| 314 | zero. |
||
| 315 | |||
| 316 | ** WARNING: Plugin parameter <x> value range is not a multiple of quantize step |
||
| 317 | |||
| 318 | A parameter's stated maximum value is not one of the possible values |
||
| 319 | obtained by adding multiples of the quantize step on to the minimum |
||
| 320 | value. |
||
| 321 | |||
| 322 | ** WARNING: Plugin parameter <x> has (more|fewer) value names than quantize steps |
||
| 323 | |||
| 324 | A quantized parameter lists some value names for its quantize steps, |
||
| 325 | but not the right number. |
||
| 326 | |||
| 327 | ** WARNING: Plugin parameter <x> default value is not a multiple of quantize |
||
| 328 | step beyond minimum |
||
| 329 | |||
| 330 | The default value for a parameter is not a value that the user could |
||
| 331 | actually obtain, if only offered the quantized values to choose from. |
||
| 332 | |||
| 333 | ** ERROR: Data returned on nonexistent output |
||
| 334 | |||
| 335 | The output number key for a returned feature is outside the range of |
||
| 336 | outputs listed in the plugin's output descriptor list. |
||
| 337 | |||
| 338 | ** NOTE: No results returned for output <x> |
||
| 339 | |||
| 340 | The plugin returned no features on one of its outputs, when given a |
||
| 341 | simple test file. This may be perfectly reasonable behaviour, but |
||
| 342 | you might like to know about it. |
||
| 343 | |||
| 344 | 62:247a0c471a51 | Chris | ** ERROR: Plugin output <x> has FixedSampleRate but gives sample rate as 0 |
| 345 | |||
| 346 | A plugin output that has a sample type of FixedSampleRate must have a |
||
| 347 | non-zero sample rate. See |
||
| 348 | https://code.soundsoftware.ac.uk/projects/vamp-plugin-sdk/wiki/SampleType |
||
| 349 | |||
| 350 | 17:ea8865f488a0 | cannam | ** NOTE: Plugin returns features with timestamps on OneSamplePerStep output |
| 351 | ** NOTE: Plugin returns features with durations on OneSamplePerStep output |
||
| 352 | |||
| 353 | Hosts will usually ignore timestamps and durations attached to any |
||
| 354 | 62:247a0c471a51 | Chris | feature returned on a OneSamplePerStep output. See |
| 355 | https://code.soundsoftware.ac.uk/projects/vamp-plugin-sdk/wiki/SampleType |
||
| 356 | 17:ea8865f488a0 | cannam | |
| 357 | ** ERROR: Plugin returns features with no timestamps on VariableSampleRate output |
||
| 358 | |||
| 359 | Timestamps are mandatory on all features associated with a |
||
| 360 | 62:247a0c471a51 | Chris | VariableSampleRate output. See |
| 361 | https://code.soundsoftware.ac.uk/projects/vamp-plugin-sdk/wiki/SampleType |
||
| 362 | 17:ea8865f488a0 | cannam | |
| 363 | ** WARNING: Plugin returned one or more NaN/inf values |
||
| 364 | |||
| 365 | The plugin returned features containing floating-point not-a-number |
||
| 366 | or infinity values. This warning may be associated with a test |
||
| 367 | involving feeding some unexpected type of data to the plugin. |
||
| 368 | |||
| 369 | ** ERROR: Consecutive runs with separate instances produce different results |
||
| 370 | |||
| 371 | The plugin was constructed and run twice against the same input data, |
||
| 372 | and returned different features each time. |
||
| 373 | |||
| 374 | If you give the -n or --nondeterministic option, vamp-plugin-tester |
||
| 375 | will downgrade this error to a note. |
||
| 376 | |||
| 377 | ** ERROR: Consecutive runs with the same instance (using reset) produce different results |
||
| 378 | |||
| 379 | The plugin was constructed, initialised, run against some input data, |
||
| 380 | reset with a call to its reset() function, and run again against the |
||
| 381 | same data; and it returned different features on each run. This is |
||
| 382 | often a sign of some simple error such as forgetting to implement |
||
| 383 | reset(). |
||
| 384 | |||
| 385 | If you give the -n or --nondeterministic option, vamp-plugin-tester |
||
| 386 | will downgrade this error to a note. |
||
| 387 | |||
| 388 | ** ERROR: Simultaneous runs with separate instances produce different results |
||
| 389 | |||
| 390 | Two instances of the plugin were constructed and run against the same |
||
| 391 | input data, giving each block of data to one plugin's process call |
||
| 392 | and then to the other's, "interleaving" the processing between the |
||
| 393 | two instances (but within a single application thread); and the two |
||
| 394 | instances returned different features. This may indicate ill-advised |
||
| 395 | use of static data shared between plugin instances. |
||
| 396 | |||
| 397 | If you give the -n or --nondeterministic option, vamp-plugin-tester |
||
| 398 | will downgrade this error to a note. |
||
| 399 | |||
| 400 | ** WARNING: Consecutive runs with different starting timestamps produce the same result |
||
| 401 | |||
| 402 | The plugin was run twice on the same audio data, but with different |
||
| 403 | input timestamps, and it returned the same results each time. While |
||
| 404 | this is often unproblematic, it can indicate that a plugin failed to |
||
| 405 | take the input timestamp into account when calculating its output |
||
| 406 | timestamps (if any). |
||
| 407 | |||
| 408 | If you give the -n or --nondeterministic option, vamp-plugin-tester |
||
| 409 | will downgrade this warning to a note. |
||
| 410 | |||
| 411 | ** ERROR: Explicitly setting current program to its supposed current value changes the results |
||
| 412 | |||
| 413 | The plugin was constructed and run twice on the same data, once |
||
| 414 | without changing its "program" setting, and again having set the |
||
| 415 | program to the vaule returned by getCurrentProgram() (i.e. the same |
||
| 416 | program that was supposed to be in effect already). It returned |
||
| 417 | different results for the two runs, suggesting that some internal |
||
| 418 | data was changed in selectProgram in a way that differed from its |
||
| 419 | default. |
||
| 420 | |||
| 421 | If you give the -n or --nondeterministic option, vamp-plugin-tester |
||
| 422 | will downgrade this error to a note. |
||
| 423 | |||
| 424 | ** ERROR: Explicitly setting parameters to their supposed default values changes the results |
||
| 425 | |||
| 426 | The plugin was constructed and run twice on the same data, once |
||
| 427 | without changing any of its parameters, and again having set the |
||
| 428 | parameters to their specified default values. It returned different |
||
| 429 | results for the two runs, suggesting that some internal data was |
||
| 430 | changed when a parameter was set to its default, in a way that |
||
| 431 | differed from the plugin's initially constructed state. |
||
| 432 | |||
| 433 | If you give the -n or --nondeterministic option, vamp-plugin-tester |
||
| 434 | will downgrade this error to a note. |
||
| 435 | |||
| 436 | 62:247a0c471a51 | Chris | ** ERROR: Call to reset after setting parameters, but before processing, changes the results (parameter values not retained through reset?) |
| 437 | |||
| 438 | The plugin was constructed and run twice on the same data. The first |
||
| 439 | time, its parameters were set to some arbitrary values and it was |
||
| 440 | initialised and run. The second time, its parameters were set to the |
||
| 441 | same values and it was initialised, then reset(), then run. The two |
||
| 442 | runs returned different results, suggesting that perhaps some |
||
| 443 | parameter value was being modified within the reset() function. (This |
||
| 444 | function should reset internal state within the plugin, but not |
||
| 445 | parameter configuration.) |
||
| 446 | |||
| 447 | If you give the -n or --nondeterministic option, vamp-plugin-tester |
||
| 448 | will downgrade this error to a note. |
||
| 449 | |||
| 450 | 17:ea8865f488a0 | cannam | ** WARNING: Constructor takes some time to run: work should be deferred to initialise? |
| 451 | |||
| 452 | The plugin took a long time to construct. You should ensure that the |
||
| 453 | constructor for the plugin runs as quickly as possible, because it |
||
| 454 | may be called by a host that is only scanning the properties of all |
||
| 455 | available plugins on startup. Any serious initialisation work should |
||
| 456 | be done in the initialise() function rather than the constructor. |
||
| 457 | |||
| 458 | |||
| 459 | 18:809fcc3d7f4e | cannam | Authors |
| 460 | ======= |
||
| 461 | |||
| 462 | This program was written at the Centre for Digital Music at Queen |
||
| 463 | 58:843e31e6df21 | Chris | Mary, University of London, by Chris Cannam. Copyright (c) 2009-2015 QMUL. |
| 464 | 18:809fcc3d7f4e | cannam | |
| 465 | 0:f89128a316e7 | cannam | /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ |
| 466 | |||
| 467 | /* |
||
| 468 | Vamp Plugin Fuzz Tester |
||
| 469 | Chris Cannam, cannam@all-day-breakfast.com |
||
| 470 | Centre for Digital Music, Queen Mary, University of London. |
||
| 471 | 42:f1e8e14e9c96 | Chris | Copyright 2009-2014 QMUL. |
| 472 | 0:f89128a316e7 | cannam | |
| 473 | This program loads a Vamp plugin and tests its susceptibility to a |
||
| 474 | number of common pitfalls, including handling of extremes of input |
||
| 475 | data. If you can think of any additional useful tests that are |
||
| 476 | easily added, please send them to me. |
||
| 477 | |||
| 478 | Permission is hereby granted, free of charge, to any person |
||
| 479 | obtaining a copy of this software and associated documentation |
||
| 480 | files (the "Software"), to deal in the Software without |
||
| 481 | restriction, including without limitation the rights to use, copy, |
||
| 482 | modify, merge, publish, distribute, sublicense, and/or sell copies |
||
| 483 | of the Software, and to permit persons to whom the Software is |
||
| 484 | furnished to do so, subject to the following conditions: |
||
| 485 | |||
| 486 | The above copyright notice and this permission notice shall be |
||
| 487 | included in all copies or substantial portions of the Software. |
||
| 488 | |||
| 489 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
||
| 490 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
||
| 491 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
||
| 492 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR |
||
| 493 | ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF |
||
| 494 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
||
| 495 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
||
| 496 | |||
| 497 | Except as contained in this notice, the names of the Centre for |
||
| 498 | Digital Music; Queen Mary, University of London; and Chris Cannam |
||
| 499 | shall not be used in advertising or otherwise to promote the sale, |
||
| 500 | use or other dealings in this Software without prior written |
||
| 501 | authorization. |
||
| 502 | */ |
||
| 503 | |||
| 504 | #include "Test.h" |
||
| 505 | |||
| 506 | #include <vamp-hostsdk/PluginLoader.h> |
||
| 507 | |||
| 508 | using namespace Vamp; |
||
| 509 | using namespace Vamp::HostExt; |
||
| 510 | |||
| 511 | 20:5af5eb2627ad | cannam | #include <math.h> |
| 512 | 1:d7ef749300ed | cannam | |
| 513 | 28:b1bc4d045a4b | cannam | #ifdef __SUNPRO_CC |
| 514 | #include <ieeefp.h> |
||
| 515 | #define isinf(x) (!finite(x)) |
||
| 516 | #endif |
||
| 517 | |||
| 518 | 0:f89128a316e7 | cannam | Test::Test() { }
|
| 519 | Test::~Test() { }
|
||
| 520 | |||
| 521 | 52:4bd0cd3c60f3 | Chris | using std::cerr; |
| 522 | using std::cout; |
||
| 523 | using std::endl; |
||
| 524 | using std::string; |
||
| 525 | |||
| 526 | 0:f89128a316e7 | cannam | Plugin * |
| 527 | 52:4bd0cd3c60f3 | Chris | Test::load(string key, float rate) |
| 528 | 0:f89128a316e7 | cannam | {
|
| 529 | 23:28097c1b3de4 | cannam | Plugin *p = PluginLoader::getInstance()->loadPlugin |
| 530 | 0:f89128a316e7 | cannam | (key, rate, PluginLoader::ADAPT_ALL); |
| 531 | 23:28097c1b3de4 | cannam | if (!p) throw FailedToLoadPlugin(); |
| 532 | return p; |
||
| 533 | 0:f89128a316e7 | cannam | } |
| 534 | |||
| 535 | 1:d7ef749300ed | cannam | float ** |
| 536 | Test::createBlock(size_t channels, size_t blocksize) |
||
| 537 | {
|
||
| 538 | float **b = new float *[channels]; |
||
| 539 | for (size_t c = 0; c < channels; ++c) {
|
||
| 540 | b[c] = new float[blocksize]; |
||
| 541 | } |
||
| 542 | return b; |
||
| 543 | } |
||
| 544 | |||
| 545 | void |
||
| 546 | Test::destroyBlock(float **blocks, size_t channels) |
||
| 547 | {
|
||
| 548 | for (size_t c = 0; c < channels; ++c) {
|
||
| 549 | delete[] blocks[c]; |
||
| 550 | } |
||
| 551 | delete[] blocks; |
||
| 552 | } |
||
| 553 | |||
| 554 | 3:0f65bb22172b | cannam | float ** |
| 555 | Test::createTestAudio(size_t channels, size_t blocksize, size_t blocks) |
||
| 556 | {
|
||
| 557 | float **b = new float *[channels]; |
||
| 558 | for (size_t c = 0; c < channels; ++c) {
|
||
| 559 | b[c] = new float[blocksize * blocks]; |
||
| 560 | for (int i = 0; i < int(blocksize * blocks); ++i) {
|
||
| 561 | b[c][i] = sinf(float(i) / 10.f); |
||
| 562 | if (i == 5005 || i == 20002) {
|
||
| 563 | b[c][i-2] = 0; |
||
| 564 | b[c][i-1] = -1; |
||
| 565 | b[c][i] = 1; |
||
| 566 | } |
||
| 567 | } |
||
| 568 | } |
||
| 569 | return b; |
||
| 570 | } |
||
| 571 | |||
| 572 | void |
||
| 573 | Test::destroyTestAudio(float **b, size_t channels) |
||
| 574 | {
|
||
| 575 | for (size_t c = 0; c < channels; ++c) {
|
||
| 576 | delete[] b[c]; |
||
| 577 | } |
||
| 578 | delete[] b; |
||
| 579 | } |
||
| 580 | |||
| 581 | 1:d7ef749300ed | cannam | bool |
| 582 | Test::initDefaults(Plugin *p, size_t &channels, size_t &step, size_t &block, |
||
| 583 | Results &r) |
||
| 584 | {
|
||
| 585 | channels = p->getMinChannelCount(); |
||
| 586 | block = p->getPreferredBlockSize(); |
||
| 587 | step = p->getPreferredStepSize(); |
||
| 588 | if (block == 0) block = 1024; |
||
| 589 | if (step == 0) {
|
||
| 590 | if (p->getInputDomain() == Plugin::FrequencyDomain) step = block/2; |
||
| 591 | else step = block; |
||
| 592 | } |
||
| 593 | if (!p->initialise(channels, step, block)) {
|
||
| 594 | r.push_back(error("initialisation with default values failed"));
|
||
| 595 | return false; |
||
| 596 | } |
||
| 597 | return true; |
||
| 598 | } |
||
| 599 | |||
| 600 | 3:0f65bb22172b | cannam | bool |
| 601 | Test::initAdapted(Plugin *p, size_t &channels, size_t step, size_t block, |
||
| 602 | Results &r) |
||
| 603 | {
|
||
| 604 | channels = p->getMinChannelCount(); |
||
| 605 | if (!p->initialise(channels, step, block)) {
|
||
| 606 | r.push_back(error("initialisation failed"));
|
||
| 607 | return false; |
||
| 608 | } |
||
| 609 | return true; |
||
| 610 | } |
||
| 611 | |||
| 612 | 0:f89128a316e7 | cannam | void |
| 613 | Test::appendFeatures(Plugin::FeatureSet &a, const Plugin::FeatureSet &b) |
||
| 614 | {
|
||
| 615 | for (Plugin::FeatureSet::const_iterator i = b.begin(); i != b.end(); ++i) {
|
||
| 616 | int output = i->first; |
||
| 617 | const Plugin::FeatureList &fl = i->second; |
||
| 618 | Plugin::FeatureList &target = a[output]; |
||
| 619 | for (Plugin::FeatureList::const_iterator j = fl.begin(); j != fl.end(); ++j) {
|
||
| 620 | target.push_back(*j); |
||
| 621 | } |
||
| 622 | } |
||
| 623 | } |
||
| 624 | |||
| 625 | bool |
||
| 626 | 1:d7ef749300ed | cannam | Test::allFeaturesValid(const Plugin::FeatureSet &b) |
| 627 | {
|
||
| 628 | for (Plugin::FeatureSet::const_iterator i = b.begin(); i != b.end(); ++i) {
|
||
| 629 | for (int j = 0; j < (int)i->second.size(); ++j) {
|
||
| 630 | if (i->second[j].values.empty()) continue; |
||
| 631 | for (int k = 0; k < (int)i->second[j].values.size(); ++k) {
|
||
| 632 | if (isnan(i->second[j].values[k]) || |
||
| 633 | isinf(i->second[j].values[k])) {
|
||
| 634 | return false; |
||
| 635 | } |
||
| 636 | } |
||
| 637 | } |
||
| 638 | } |
||
| 639 | return true; |
||
| 640 | } |
||
| 641 | |||
| 642 | 53:86d8a699dfbe | Chris | bool |
| 643 | Test::containsTimestamps(const Plugin::FeatureSet &b) |
||
| 644 | {
|
||
| 645 | for (Plugin::FeatureSet::const_iterator i = b.begin(); i != b.end(); ++i) {
|
||
| 646 | for (int j = 0; j < (int)i->second.size(); ++j) {
|
||
| 647 | if (i->second[j].values.empty()) continue; |
||
| 648 | for (int k = 0; k < (int)i->second[j].values.size(); ++k) {
|
||
| 649 | if (i->second[j].hasTimestamp) {
|
||
| 650 | return true; |
||
| 651 | } |
||
| 652 | } |
||
| 653 | } |
||
| 654 | } |
||
| 655 | return false; |
||
| 656 | } |
||
| 657 | |||
| 658 | 3:0f65bb22172b | cannam | void |
| 659 | 61:c7fd03f5ae02 | Chris | Test::dumpFeature(const Plugin::Feature &f, bool showValues, |
| 660 | const Plugin::Feature *other) |
||
| 661 | 52:4bd0cd3c60f3 | Chris | {
|
| 662 | cout << " Timestamp: " << (!f.hasTimestamp ? "(none)" : f.timestamp.toText()) << endl; |
||
| 663 | cout << " Duration: " << (!f.hasDuration ? "(none)" : f.duration.toText()) << endl; |
||
| 664 | cout << " Label: " << (f.label == "" ? "(none)" : f.label) << endl; |
||
| 665 | if (showValues) {
|
||
| 666 | cout << " Values (" << f.values.size() << "): " << (f.values.empty() ? "(none)" : "");
|
||
| 667 | 61:c7fd03f5ae02 | Chris | int n = f.values.size(); |
| 668 | if (!other) {
|
||
| 669 | for (int j = 0; j < n; ++j) {
|
||
| 670 | cout << f.values[j] << " "; |
||
| 671 | } |
||
| 672 | } else {
|
||
| 673 | int samecount = 0; |
||
| 674 | int diffcount = 0; |
||
| 675 | for (int j = 0; j <= n; ++j) {
|
||
| 676 | if (j < n && f.values[j] == other->values[j]) {
|
||
| 677 | ++samecount; |
||
| 678 | } else {
|
||
| 679 | if (samecount > 0) {
|
||
| 680 | cout << "(" << samecount << " identical) ";
|
||
| 681 | } |
||
| 682 | samecount = 0; |
||
| 683 | if (j < n) {
|
||
| 684 | ++diffcount; |
||
| 685 | if (diffcount > 20 && j + 10 < n) {
|
||
| 686 | cout << "(remaining " << n - j << " values elided)"; |
||
| 687 | break; |
||
| 688 | } else {
|
||
| 689 | cout << f.values[j] << " [diff " |
||
| 690 | << f.values[j] - other->values[j] << "] "; |
||
| 691 | } |
||
| 692 | } |
||
| 693 | } |
||
| 694 | } |
||
| 695 | 52:4bd0cd3c60f3 | Chris | } |
| 696 | cout << endl; |
||
| 697 | } else {
|
||
| 698 | cout << " Values (" << f.values.size() << "): (elided)" << endl;
|
||
| 699 | } |
||
| 700 | } |
||
| 701 | |||
| 702 | void |
||
| 703 | Test::dump(const Plugin::FeatureSet &fs, bool showValues) |
||
| 704 | 3:0f65bb22172b | cannam | {
|
| 705 | for (Plugin::FeatureSet::const_iterator fsi = fs.begin(); |
||
| 706 | fsi != fs.end(); ++fsi) {
|
||
| 707 | int output = fsi->first; |
||
| 708 | 52:4bd0cd3c60f3 | Chris | cout << "Output " << output << ":" << endl; |
| 709 | 3:0f65bb22172b | cannam | const Plugin::FeatureList &fl = fsi->second; |
| 710 | for (int i = 0; i < (int)fl.size(); ++i) {
|
||
| 711 | 52:4bd0cd3c60f3 | Chris | cout << " Feature " << i << ":" << endl; |
| 712 | 3:0f65bb22172b | cannam | const Plugin::Feature &f = fl[i]; |
| 713 | 52:4bd0cd3c60f3 | Chris | dumpFeature(f, showValues); |
| 714 | 3:0f65bb22172b | cannam | } |
| 715 | } |
||
| 716 | } |
||
| 717 | |||
| 718 | void |
||
| 719 | 52:4bd0cd3c60f3 | Chris | Test::dumpTwo(const Result &r, |
| 720 | const Plugin::FeatureSet &a, |
||
| 721 | const Plugin::FeatureSet &b) |
||
| 722 | 3:0f65bb22172b | cannam | {
|
| 723 | 8:3019cb6b538d | cannam | std::cout << r.message() << std::endl; |
| 724 | std::cout << "\nFirst result set:" << std::endl; |
||
| 725 | 52:4bd0cd3c60f3 | Chris | dump(a, false); |
| 726 | 8:3019cb6b538d | cannam | std::cout << "\nSecond result set:" << std::endl; |
| 727 | 52:4bd0cd3c60f3 | Chris | dump(b, false); |
| 728 | 8:3019cb6b538d | cannam | std::cout << std::endl; |
| 729 | 3:0f65bb22172b | cannam | } |
| 730 | |||
| 731 | 52:4bd0cd3c60f3 | Chris | void |
| 732 | Test::dumpDiff(const Result &r, |
||
| 733 | const Plugin::FeatureSet &a, |
||
| 734 | const Plugin::FeatureSet &b) |
||
| 735 | {
|
||
| 736 | cout << r.message() << endl; |
||
| 737 | cout << "\nDifferences follow:" << endl; |
||
| 738 | if (a.size() != b.size()) {
|
||
| 739 | cout << "*** First result set has features on " << a.size() |
||
| 740 | << " output(s), second has features on " << b.size() |
||
| 741 | << endl; |
||
| 742 | return; |
||
| 743 | } |
||
| 744 | Plugin::FeatureSet::const_iterator ai = a.begin(); |
||
| 745 | Plugin::FeatureSet::const_iterator bi = b.begin(); |
||
| 746 | while (ai != a.end()) {
|
||
| 747 | if (ai->first != bi->first) {
|
||
| 748 | cout << "\n*** Output number mismatch: first result set says " |
||
| 749 | << ai->first << " where second says " << bi->first |
||
| 750 | << endl; |
||
| 751 | } else {
|
||
| 752 | cout << "\nOutput " << ai->first << ":" << endl; |
||
| 753 | if (ai->second.size() != bi->second.size()) {
|
||
| 754 | cout << "*** First result set has " << ai->second.size() |
||
| 755 | << " feature(s) on this output, second has " |
||
| 756 | << bi->second.size() << endl; |
||
| 757 | } else {
|
||
| 758 | int fno = 0; |
||
| 759 | int diffcount = 0; |
||
| 760 | Plugin::FeatureList::const_iterator afi = ai->second.begin(); |
||
| 761 | Plugin::FeatureList::const_iterator bfi = bi->second.begin(); |
||
| 762 | while (afi != ai->second.end()) {
|
||
| 763 | if (!(*afi == *bfi)) {
|
||
| 764 | if (diffcount == 0) {
|
||
| 765 | 61:c7fd03f5ae02 | Chris | bool differInValues = |
| 766 | (afi->values.size() == bfi->values.size() && |
||
| 767 | afi->values != bfi->values); |
||
| 768 | 52:4bd0cd3c60f3 | Chris | if (afi->hasTimestamp != bfi->hasTimestamp) {
|
| 769 | cout << "*** Feature " << fno << " differs in presence of timestamp (" << afi->hasTimestamp << " vs " << bfi->hasTimestamp << ")" << endl;
|
||
| 770 | } |
||
| 771 | if (afi->hasTimestamp && (afi->timestamp != bfi->timestamp)) {
|
||
| 772 | cout << "*** Feature " << fno << " differs in timestamp (" << afi->timestamp << " vs " << bfi->timestamp << " )" << endl;
|
||
| 773 | } |
||
| 774 | if (afi->hasDuration != bfi->hasDuration) {
|
||
| 775 | cout << "*** Feature " << fno << " differs in presence of duration (" << afi->hasDuration << " vs " << bfi->hasDuration << ")" << endl;
|
||
| 776 | } |
||
| 777 | if (afi->hasDuration && (afi->duration != bfi->duration)) {
|
||
| 778 | cout << "*** Feature " << fno << " differs in duration (" << afi->duration << " vs " << bfi->duration << " )" << endl;
|
||
| 779 | } |
||
| 780 | if (afi->label != bfi->label) {
|
||
| 781 | cout << "*** Feature " << fno << " differs in label" << endl; |
||
| 782 | } |
||
| 783 | 61:c7fd03f5ae02 | Chris | if (afi->values.size() != bfi->values.size()) {
|
| 784 | cout << "*** Feature " << fno << " differs in number of values (" << afi->values.size() << " vs " << bfi->values.size() << ")" << endl;
|
||
| 785 | } |
||
| 786 | 52:4bd0cd3c60f3 | Chris | if (differInValues) {
|
| 787 | cout << "*** Feature " << fno << " differs in values" << endl; |
||
| 788 | } |
||
| 789 | cout << " First output:" << endl; |
||
| 790 | dumpFeature(*afi, differInValues); |
||
| 791 | cout << " Second output:" << endl; |
||
| 792 | 61:c7fd03f5ae02 | Chris | dumpFeature(*bfi, differInValues, &(*afi)); |
| 793 | 52:4bd0cd3c60f3 | Chris | } |
| 794 | ++diffcount; |
||
| 795 | } |
||
| 796 | ++fno; |
||
| 797 | ++afi; |
||
| 798 | ++bfi; |
||
| 799 | } |
||
| 800 | if (diffcount > 1) {
|
||
| 801 | cout << diffcount-1 << " subsequent differing feature(s) elided" << endl; |
||
| 802 | } |
||
| 803 | } |
||
| 804 | } |
||
| 805 | ++ai; |
||
| 806 | ++bi; |
||
| 807 | } |
||
| 808 | cout << endl; |
||
| 809 | } |
||
| 810 | |||
| 811 | 1:d7ef749300ed | cannam | bool |
| 812 | 0:f89128a316e7 | cannam | operator==(const Plugin::FeatureSet &a, const Plugin::FeatureSet &b) |
| 813 | {
|
||
| 814 | if (a.size() != b.size()) return false; |
||
| 815 | for (Plugin::FeatureSet::const_iterator ai = a.begin(); |
||
| 816 | ai != a.end(); ++ai) {
|
||
| 817 | int output = ai->first; |
||
| 818 | Plugin::FeatureSet::const_iterator bi = b.find(output); |
||
| 819 | if (bi == b.end()) return false; |
||
| 820 | if (!(ai->second == bi->second)) return false; |
||
| 821 | } |
||
| 822 | return true; |
||
| 823 | } |
||
| 824 | |||
| 825 | bool |
||
| 826 | operator==(const Plugin::FeatureList &a, const Plugin::FeatureList &b) |
||
| 827 | {
|
||
| 828 | if (a.size() != b.size()) return false; |
||
| 829 | for (int i = 0; i < (int)a.size(); ++i) {
|
||
| 830 | if (!(a[i] == b[i])) return false; |
||
| 831 | } |
||
| 832 | return true; |
||
| 833 | } |
||
| 834 | |||
| 835 | bool |
||
| 836 | operator==(const Plugin::Feature &a, const Plugin::Feature &b) |
||
| 837 | {
|
||
| 838 | if (a.hasTimestamp != b.hasTimestamp) return false; |
||
| 839 | if (a.hasTimestamp && (a.timestamp != b.timestamp)) return false; |
||
| 840 | if (a.hasDuration != b.hasDuration) return false; |
||
| 841 | if (a.hasDuration && (a.duration != b.duration)) return false; |
||
| 842 | if (a.values != b.values) return false; |
||
| 843 | if (a.label != b.label) return false; |
||
| 844 | return true; |
||
| 845 | } |
||
| 846 | |||
| 847 | /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ |
||
| 848 | |||
| 849 | /* |
||
| 850 | Vamp Plugin Tester |
||
| 851 | Chris Cannam, cannam@all-day-breakfast.com |
||
| 852 | Centre for Digital Music, Queen Mary, University of London. |
||
| 853 | 42:f1e8e14e9c96 | Chris | Copyright 2009-2014 QMUL. |
| 854 | 0:f89128a316e7 | cannam | |
| 855 | This program loads a Vamp plugin and tests its susceptibility to a |
||
| 856 | number of common pitfalls, including handling of extremes of input |
||
| 857 | data. If you can think of any additional useful tests that are |
||
| 858 | easily added, please send them to me. |
||
| 859 | |||
| 860 | Permission is hereby granted, free of charge, to any person |
||
| 861 | obtaining a copy of this software and associated documentation |
||
| 862 | files (the "Software"), to deal in the Software without |
||
| 863 | restriction, including without limitation the rights to use, copy, |
||
| 864 | modify, merge, publish, distribute, sublicense, and/or sell copies |
||
| 865 | of the Software, and to permit persons to whom the Software is |
||
| 866 | furnished to do so, subject to the following conditions: |
||
| 867 | |||
| 868 | The above copyright notice and this permission notice shall be |
||
| 869 | included in all copies or substantial portions of the Software. |
||
| 870 | |||
| 871 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
||
| 872 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
||
| 873 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
||
| 874 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR |
||
| 875 | ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF |
||
| 876 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
||
| 877 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
||
| 878 | |||
| 879 | Except as contained in this notice, the names of the Centre for |
||
| 880 | Digital Music; Queen Mary, University of London; and Chris Cannam |
||
| 881 | shall not be used in advertising or otherwise to promote the sale, |
||
| 882 | use or other dealings in this Software without prior written |
||
| 883 | authorization. |
||
| 884 | */ |
||
| 885 | |||
| 886 | #ifndef _TEST_H_ |
||
| 887 | #define _TEST_H_ |
||
| 888 | |||
| 889 | #include <string> |
||
| 890 | |||
| 891 | #include <vamp-hostsdk/Plugin.h> |
||
| 892 | |||
| 893 | class Test |
||
| 894 | {
|
||
| 895 | public: |
||
| 896 | virtual ~Test(); |
||
| 897 | 8:3019cb6b538d | cannam | |
| 898 | enum Option {
|
||
| 899 | 39:07144cdcbedf | Chris | NoOption = 0x0, |
| 900 | NonDeterministic = 0x1, |
||
| 901 | Verbose = 0x2, |
||
| 902 | SingleTest = 0x4 |
||
| 903 | 8:3019cb6b538d | cannam | }; |
| 904 | typedef int Options; |
||
| 905 | 0:f89128a316e7 | cannam | |
| 906 | class Result {
|
||
| 907 | |||
| 908 | public: |
||
| 909 | 3:0f65bb22172b | cannam | enum Code { Success, Note, Warning, Error };
|
| 910 | 0:f89128a316e7 | cannam | |
| 911 | 8:3019cb6b538d | cannam | Result() : m_code(Success) { }
|
| 912 | 0:f89128a316e7 | cannam | Result(Code c, std::string m) : m_code(c), m_message(m) { }
|
| 913 | |||
| 914 | 3:0f65bb22172b | cannam | Code code() const { return m_code; }
|
| 915 | std::string message() const { return m_message; }
|
||
| 916 | 8:3019cb6b538d | cannam | |
| 917 | 0:f89128a316e7 | cannam | protected: |
| 918 | Code m_code; |
||
| 919 | std::string m_message; |
||
| 920 | }; |
||
| 921 | |||
| 922 | static Result success() { return Result(Result::Success, ""); }
|
||
| 923 | 3:0f65bb22172b | cannam | static Result note(std::string m) { return Result(Result::Note, m); }
|
| 924 | 0:f89128a316e7 | cannam | static Result warning(std::string m) { return Result(Result::Warning, m); }
|
| 925 | static Result error(std::string m) { return Result(Result::Error, m); }
|
||
| 926 | |||
| 927 | typedef std::vector<Result> Results; |
||
| 928 | |||
| 929 | class FailedToLoadPlugin { };
|
||
| 930 | |||
| 931 | // may throw FailedToLoadPlugin |
||
| 932 | 8:3019cb6b538d | cannam | virtual Results test(std::string key, Options) = 0; |
| 933 | 0:f89128a316e7 | cannam | |
| 934 | protected: |
||
| 935 | Test(); |
||
| 936 | |||
| 937 | // may throw FailedToLoadPlugin |
||
| 938 | Vamp::Plugin *load(std::string key, float rate = 44100); |
||
| 939 | |||
| 940 | 1:d7ef749300ed | cannam | float **createBlock(size_t channels, size_t blocksize); |
| 941 | void destroyBlock(float **blocks, size_t channels); |
||
| 942 | |||
| 943 | 3:0f65bb22172b | cannam | float **createTestAudio(size_t channels, size_t blocksize, size_t blocks); |
| 944 | void destroyTestAudio(float **audio, size_t channels); |
||
| 945 | |||
| 946 | 4:d8724c5a6d83 | cannam | // use plugin's preferred step/block size, return them: |
| 947 | 1:d7ef749300ed | cannam | bool initDefaults(Vamp::Plugin *, size_t &channels, |
| 948 | size_t &step, size_t &block, Results &r); |
||
| 949 | |||
| 950 | 4:d8724c5a6d83 | cannam | // use the given step/block size and an adapter: |
| 951 | 3:0f65bb22172b | cannam | bool initAdapted(Vamp::Plugin *, size_t &channels, |
| 952 | size_t step, size_t block, Results &r); |
||
| 953 | |||
| 954 | 0:f89128a316e7 | cannam | void appendFeatures(Vamp::Plugin::FeatureSet &a, |
| 955 | const Vamp::Plugin::FeatureSet &b); |
||
| 956 | 1:d7ef749300ed | cannam | |
| 957 | bool allFeaturesValid(const Vamp::Plugin::FeatureSet &); // i.e. no NaN/inf |
||
| 958 | 3:0f65bb22172b | cannam | |
| 959 | 53:86d8a699dfbe | Chris | bool containsTimestamps(const Vamp::Plugin::FeatureSet &); |
| 960 | |||
| 961 | 61:c7fd03f5ae02 | Chris | void dumpFeature(const Vamp::Plugin::Feature &, bool showValues, |
| 962 | const Vamp::Plugin::Feature *other = 0); |
||
| 963 | 52:4bd0cd3c60f3 | Chris | void dump(const Vamp::Plugin::FeatureSet &, bool showValues = true); |
| 964 | void dumpTwo(const Result &r, |
||
| 965 | const Vamp::Plugin::FeatureSet &, |
||
| 966 | const Vamp::Plugin::FeatureSet &); |
||
| 967 | void dumpDiff(const Result &r, |
||
| 968 | const Vamp::Plugin::FeatureSet &, |
||
| 969 | const Vamp::Plugin::FeatureSet &); |
||
| 970 | 0:f89128a316e7 | cannam | }; |
| 971 | |||
| 972 | extern bool operator==(const Vamp::Plugin::FeatureSet &a, |
||
| 973 | const Vamp::Plugin::FeatureSet &b); |
||
| 974 | extern bool operator==(const Vamp::Plugin::FeatureList &a, |
||
| 975 | const Vamp::Plugin::FeatureList &b); |
||
| 976 | extern bool operator==(const Vamp::Plugin::Feature &a, |
||
| 977 | const Vamp::Plugin::Feature &b); |
||
| 978 | |||
| 979 | #endif |
||
| 980 | |||
| 981 | 5:6a279da6fdd7 | cannam | /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ |
| 982 | |||
| 983 | /* |
||
| 984 | Vamp Plugin Tester |
||
| 985 | Chris Cannam, cannam@all-day-breakfast.com |
||
| 986 | Centre for Digital Music, Queen Mary, University of London. |
||
| 987 | 42:f1e8e14e9c96 | Chris | Copyright 2009-2014 QMUL. |
| 988 | 5:6a279da6fdd7 | cannam | |
| 989 | This program loads a Vamp plugin and tests its susceptibility to a |
||
| 990 | number of common pitfalls, including handling of extremes of input |
||
| 991 | data. If you can think of any additional useful tests that are |
||
| 992 | easily added, please send them to me. |
||
| 993 | |||
| 994 | Permission is hereby granted, free of charge, to any person |
||
| 995 | obtaining a copy of this software and associated documentation |
||
| 996 | files (the "Software"), to deal in the Software without |
||
| 997 | restriction, including without limitation the rights to use, copy, |
||
| 998 | modify, merge, publish, distribute, sublicense, and/or sell copies |
||
| 999 | of the Software, and to permit persons to whom the Software is |
||
| 1000 | furnished to do so, subject to the following conditions: |
||
| 1001 | |||
| 1002 | The above copyright notice and this permission notice shall be |
||
| 1003 | included in all copies or substantial portions of the Software. |
||
| 1004 | |||
| 1005 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
||
| 1006 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
||
| 1007 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
||
| 1008 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR |
||
| 1009 | ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF |
||
| 1010 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
||
| 1011 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
||
| 1012 | |||
| 1013 | Except as contained in this notice, the names of the Centre for |
||
| 1014 | Digital Music; Queen Mary, University of London; and Chris Cannam |
||
| 1015 | shall not be used in advertising or otherwise to promote the sale, |
||
| 1016 | use or other dealings in this Software without prior written |
||
| 1017 | authorization. |
||
| 1018 | */ |
||
| 1019 | |||
| 1020 | #include "TestDefaults.h" |
||
| 1021 | |||
| 1022 | #include <vamp-hostsdk/Plugin.h> |
||
| 1023 | using namespace Vamp; |
||
| 1024 | |||
| 1025 | #include <memory> |
||
| 1026 | using namespace std; |
||
| 1027 | |||
| 1028 | #include <cmath> |
||
| 1029 | 6:ba3c8cc649d3 | cannam | #include <time.h> |
| 1030 | 5:6a279da6fdd7 | cannam | |
| 1031 | Tester::TestRegistrar<TestDefaultProgram> |
||
| 1032 | 39:07144cdcbedf | Chris | TestDefaultProgram::m_registrar("E1", "Inconsistent default program");
|
| 1033 | 5:6a279da6fdd7 | cannam | |
| 1034 | Tester::TestRegistrar<TestDefaultParameters> |
||
| 1035 | 39:07144cdcbedf | Chris | TestDefaultParameters::m_registrar("E2", "Inconsistent default parameters");
|
| 1036 | 5:6a279da6fdd7 | cannam | |
| 1037 | 34:a2d9aed55a2a | Chris | Tester::TestRegistrar<TestParametersOnReset> |
| 1038 | 39:07144cdcbedf | Chris | TestParametersOnReset::m_registrar("E3", "Parameter retention through reset");
|
| 1039 | 34:a2d9aed55a2a | Chris | |
| 1040 | 5:6a279da6fdd7 | cannam | static const size_t _step = 1000; |
| 1041 | |||
| 1042 | Test::Results |
||
| 1043 | 8:3019cb6b538d | cannam | TestDefaultProgram::test(string key, Options options) |
| 1044 | 5:6a279da6fdd7 | cannam | {
|
| 1045 | Plugin::FeatureSet f[2]; |
||
| 1046 | int rate = 44100; |
||
| 1047 | Results r; |
||
| 1048 | float **data = 0; |
||
| 1049 | size_t channels = 0; |
||
| 1050 | size_t count = 100; |
||
| 1051 | |||
| 1052 | for (int run = 0; run < 2; ++run) {
|
||
| 1053 | auto_ptr<Plugin> p(load(key, rate)); |
||
| 1054 | if (p->getPrograms().empty()) return r; |
||
| 1055 | if (run == 1) {
|
||
| 1056 | p->selectProgram(p->getCurrentProgram()); |
||
| 1057 | } |
||
| 1058 | if (!initAdapted(p.get(), channels, _step, _step, r)) return r; |
||
| 1059 | if (!data) data = createTestAudio(channels, _step, count); |
||
| 1060 | for (size_t i = 0; i < count; ++i) {
|
||
| 1061 | 67:fa66ee7dcf08 | Chris | float **ptr = new float *[channels]; |
| 1062 | 5:6a279da6fdd7 | cannam | size_t idx = i * _step; |
| 1063 | for (size_t c = 0; c < channels; ++c) ptr[c] = data[c] + idx; |
||
| 1064 | RealTime timestamp = RealTime::frame2RealTime(idx, rate); |
||
| 1065 | Plugin::FeatureSet fs = p->process(ptr, timestamp); |
||
| 1066 | 67:fa66ee7dcf08 | Chris | delete[] ptr; |
| 1067 | 5:6a279da6fdd7 | cannam | appendFeatures(f[run], fs); |
| 1068 | } |
||
| 1069 | Plugin::FeatureSet fs = p->getRemainingFeatures(); |
||
| 1070 | appendFeatures(f[run], fs); |
||
| 1071 | } |
||
| 1072 | if (data) destroyTestAudio(data, channels); |
||
| 1073 | |||
| 1074 | if (!(f[0] == f[1])) {
|
||
| 1075 | 8:3019cb6b538d | cannam | string message = "Explicitly setting current program to its supposed current value changes the results"; |
| 1076 | Result res; |
||
| 1077 | if (options & NonDeterministic) res = note(message); |
||
| 1078 | else res = error(message); |
||
| 1079 | 52:4bd0cd3c60f3 | Chris | if (options & Verbose) dumpDiff(res, f[0], f[1]); |
| 1080 | 7:43eb3a4b95c8 | cannam | r.push_back(res); |
| 1081 | 5:6a279da6fdd7 | cannam | } else {
|
| 1082 | r.push_back(success()); |
||
| 1083 | } |
||
| 1084 | |||
| 1085 | return r; |
||
| 1086 | } |
||
| 1087 | |||
| 1088 | Test::Results |
||
| 1089 | 8:3019cb6b538d | cannam | TestDefaultParameters::test(string key, Options options) |
| 1090 | 5:6a279da6fdd7 | cannam | {
|
| 1091 | Plugin::FeatureSet f[2]; |
||
| 1092 | int rate = 44100; |
||
| 1093 | Results r; |
||
| 1094 | float **data = 0; |
||
| 1095 | size_t channels = 0; |
||
| 1096 | size_t count = 100; |
||
| 1097 | |||
| 1098 | for (int run = 0; run < 2; ++run) {
|
||
| 1099 | auto_ptr<Plugin> p(load(key, rate)); |
||
| 1100 | if (p->getParameterDescriptors().empty()) return r; |
||
| 1101 | if (run == 1) {
|
||
| 1102 | Plugin::ParameterList pl = p->getParameterDescriptors(); |
||
| 1103 | for (int i = 0; i < (int)pl.size(); ++i) {
|
||
| 1104 | if (p->getParameter(pl[i].identifier) != pl[i].defaultValue) {
|
||
| 1105 | 23:28097c1b3de4 | cannam | if (options & Verbose) {
|
| 1106 | cout << "Parameter: " << pl[i].identifier << endl; |
||
| 1107 | cout << "Expected: " << pl[i].defaultValue << endl; |
||
| 1108 | cout << "Actual: " << p->getParameter(pl[i].identifier) << endl; |
||
| 1109 | } |
||
| 1110 | 5:6a279da6fdd7 | cannam | r.push_back(error("Not all parameters have their default values when queried directly after construction"));
|
| 1111 | } |
||
| 1112 | p->setParameter(pl[i].identifier, pl[i].defaultValue); |
||
| 1113 | } |
||
| 1114 | } |
||
| 1115 | if (!initAdapted(p.get(), channels, _step, _step, r)) return r; |
||
| 1116 | if (!data) data = createTestAudio(channels, _step, count); |
||
| 1117 | for (size_t i = 0; i < count; ++i) {
|
||
| 1118 | 67:fa66ee7dcf08 | Chris | float **ptr = new float *[channels]; |
| 1119 | 5:6a279da6fdd7 | cannam | size_t idx = i * _step; |
| 1120 | for (size_t c = 0; c < channels; ++c) ptr[c] = data[c] + idx; |
||
| 1121 | RealTime timestamp = RealTime::frame2RealTime(idx, rate); |
||
| 1122 | Plugin::FeatureSet fs = p->process(ptr, timestamp); |
||
| 1123 | 67:fa66ee7dcf08 | Chris | delete[] ptr; |
| 1124 | 5:6a279da6fdd7 | cannam | appendFeatures(f[run], fs); |
| 1125 | } |
||
| 1126 | Plugin::FeatureSet fs = p->getRemainingFeatures(); |
||
| 1127 | appendFeatures(f[run], fs); |
||
| 1128 | } |
||
| 1129 | if (data) destroyTestAudio(data, channels); |
||
| 1130 | |||
| 1131 | if (!(f[0] == f[1])) {
|
||
| 1132 | 8:3019cb6b538d | cannam | string message = "Explicitly setting parameters to their supposed default values changes the results"; |
| 1133 | Result res; |
||
| 1134 | if (options & NonDeterministic) res = note(message); |
||
| 1135 | else res = error(message); |
||
| 1136 | 52:4bd0cd3c60f3 | Chris | if (options & Verbose) dumpDiff(res, f[0], f[1]); |
| 1137 | 7:43eb3a4b95c8 | cannam | r.push_back(res); |
| 1138 | 5:6a279da6fdd7 | cannam | } else {
|
| 1139 | r.push_back(success()); |
||
| 1140 | } |
||
| 1141 | |||
| 1142 | return r; |
||
| 1143 | } |
||
| 1144 | 34:a2d9aed55a2a | Chris | |
| 1145 | Test::Results |
||
| 1146 | TestParametersOnReset::test(string key, Options options) |
||
| 1147 | {
|
||
| 1148 | Plugin::FeatureSet f[2]; |
||
| 1149 | int rate = 44100; |
||
| 1150 | Results r; |
||
| 1151 | float **data = 0; |
||
| 1152 | size_t channels = 0; |
||
| 1153 | size_t count = 100; |
||
| 1154 | |||
| 1155 | for (int run = 0; run < 2; ++run) {
|
||
| 1156 | auto_ptr<Plugin> p(load(key, rate)); |
||
| 1157 | if (p->getParameterDescriptors().empty()) return r; |
||
| 1158 | |||
| 1159 | // Set all parameters to non-default values |
||
| 1160 | 36:7d4c98c696a5 | Chris | |
| 1161 | 34:a2d9aed55a2a | Chris | Plugin::ParameterList pl = p->getParameterDescriptors(); |
| 1162 | 36:7d4c98c696a5 | Chris | |
| 1163 | 34:a2d9aed55a2a | Chris | for (int i = 0; i < (int)pl.size(); ++i) {
|
| 1164 | 36:7d4c98c696a5 | Chris | |
| 1165 | // Half-way between default and max value, seems a |
||
| 1166 | // reasonable guess for something to set it to. We want to |
||
| 1167 | // avoid the real extremes because they can sometimes be |
||
| 1168 | // very slow, and we want to avoid setting everything to |
||
| 1169 | // the same values (e.g. min) because plugins will |
||
| 1170 | // sometimes legitimately reject that. |
||
| 1171 | |||
| 1172 | // Remember to take into account quantization |
||
| 1173 | |||
| 1174 | float value = (pl[i].defaultValue + pl[i].maxValue) / 2; |
||
| 1175 | |||
| 1176 | if (pl[i].isQuantized) {
|
||
| 1177 | value = round(value / pl[i].quantizeStep) * pl[i].quantizeStep; |
||
| 1178 | 34:a2d9aed55a2a | Chris | } |
| 1179 | 36:7d4c98c696a5 | Chris | |
| 1180 | if (value > pl[i].maxValue) {
|
||
| 1181 | value = pl[i].maxValue; |
||
| 1182 | } |
||
| 1183 | if (value < pl[i].minValue) {
|
||
| 1184 | value = pl[i].minValue; |
||
| 1185 | } |
||
| 1186 | if (value == pl[i].defaultValue) {
|
||
| 1187 | if (pl[i].defaultValue == pl[i].minValue) {
|
||
| 1188 | value = pl[i].maxValue; |
||
| 1189 | } else {
|
||
| 1190 | value = pl[i].minValue; |
||
| 1191 | } |
||
| 1192 | } |
||
| 1193 | |||
| 1194 | p->setParameter(pl[i].identifier, value); |
||
| 1195 | 34:a2d9aed55a2a | Chris | } |
| 1196 | |||
| 1197 | 52:4bd0cd3c60f3 | Chris | if (!initAdapted(p.get(), channels, _step, _step, r)) {
|
| 1198 | |||
| 1199 | // OK, plugin didn't like that. Let's try a different tack |
||
| 1200 | // -- set everything to min except those parameters whose |
||
| 1201 | // default is min, and set those to half way instead |
||
| 1202 | |||
| 1203 | for (int i = 0; i < (int)pl.size(); ++i) {
|
||
| 1204 | float value = pl[i].minValue; |
||
| 1205 | if (value == pl[i].defaultValue) {
|
||
| 1206 | value = (pl[i].maxValue + pl[i].minValue) / 2; |
||
| 1207 | value = ceil(value / pl[i].quantizeStep) * pl[i].quantizeStep; |
||
| 1208 | if (value > pl[i].maxValue) {
|
||
| 1209 | value = pl[i].maxValue; |
||
| 1210 | } |
||
| 1211 | } |
||
| 1212 | p->setParameter(pl[i].identifier, value); |
||
| 1213 | } |
||
| 1214 | |||
| 1215 | r = Results(); |
||
| 1216 | if (!initAdapted(p.get(), channels, _step, _step, r)) {
|
||
| 1217 | // Still didn't work, give up |
||
| 1218 | return r; |
||
| 1219 | } |
||
| 1220 | } |
||
| 1221 | 34:a2d9aed55a2a | Chris | |
| 1222 | // First run: construct, set params, init, process |
||
| 1223 | // Second run: construct, set params, init, reset, process |
||
| 1224 | // We expect these to produce the same results |
||
| 1225 | if (run == 1) p->reset(); |
||
| 1226 | |||
| 1227 | if (!data) data = createTestAudio(channels, _step, count); |
||
| 1228 | for (size_t i = 0; i < count; ++i) {
|
||
| 1229 | 67:fa66ee7dcf08 | Chris | float **ptr = new float *[channels]; |
| 1230 | 34:a2d9aed55a2a | Chris | size_t idx = i * _step; |
| 1231 | for (size_t c = 0; c < channels; ++c) ptr[c] = data[c] + idx; |
||
| 1232 | RealTime timestamp = RealTime::frame2RealTime(idx, rate); |
||
| 1233 | Plugin::FeatureSet fs = p->process(ptr, timestamp); |
||
| 1234 | 67:fa66ee7dcf08 | Chris | delete[] ptr; |
| 1235 | 34:a2d9aed55a2a | Chris | appendFeatures(f[run], fs); |
| 1236 | } |
||
| 1237 | Plugin::FeatureSet fs = p->getRemainingFeatures(); |
||
| 1238 | appendFeatures(f[run], fs); |
||
| 1239 | } |
||
| 1240 | if (data) destroyTestAudio(data, channels); |
||
| 1241 | |||
| 1242 | if (!(f[0] == f[1])) {
|
||
| 1243 | string message = "Call to reset after setting parameters, but before processing, changes the results (parameter values not retained through reset?)"; |
||
| 1244 | Result res; |
||
| 1245 | if (options & NonDeterministic) res = note(message); |
||
| 1246 | else res = error(message); |
||
| 1247 | 52:4bd0cd3c60f3 | Chris | if (options & Verbose) dumpDiff(res, f[0], f[1]); |
| 1248 | 34:a2d9aed55a2a | Chris | r.push_back(res); |
| 1249 | } else {
|
||
| 1250 | r.push_back(success()); |
||
| 1251 | } |
||
| 1252 | |||
| 1253 | return r; |
||
| 1254 | } |
||
| 1255 | 5:6a279da6fdd7 | cannam | /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ |
| 1256 | |||
| 1257 | /* |
||
| 1258 | Vamp Plugin Tester |
||
| 1259 | Chris Cannam, cannam@all-day-breakfast.com |
||
| 1260 | Centre for Digital Music, Queen Mary, University of London. |
||
| 1261 | 42:f1e8e14e9c96 | Chris | Copyright 2009-2014 QMUL. |
| 1262 | 5:6a279da6fdd7 | cannam | |
| 1263 | This program loads a Vamp plugin and tests its susceptibility to a |
||
| 1264 | number of common pitfalls, including handling of extremes of input |
||
| 1265 | data. If you can think of any additional useful tests that are |
||
| 1266 | easily added, please send them to me. |
||
| 1267 | |||
| 1268 | Permission is hereby granted, free of charge, to any person |
||
| 1269 | obtaining a copy of this software and associated documentation |
||
| 1270 | files (the "Software"), to deal in the Software without |
||
| 1271 | restriction, including without limitation the rights to use, copy, |
||
| 1272 | modify, merge, publish, distribute, sublicense, and/or sell copies |
||
| 1273 | of the Software, and to permit persons to whom the Software is |
||
| 1274 | furnished to do so, subject to the following conditions: |
||
| 1275 | |||
| 1276 | The above copyright notice and this permission notice shall be |
||
| 1277 | included in all copies or substantial portions of the Software. |
||
| 1278 | |||
| 1279 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
||
| 1280 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
||
| 1281 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
||
| 1282 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR |
||
| 1283 | ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF |
||
| 1284 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
||
| 1285 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
||
| 1286 | |||
| 1287 | Except as contained in this notice, the names of the Centre for |
||
| 1288 | Digital Music; Queen Mary, University of London; and Chris Cannam |
||
| 1289 | shall not be used in advertising or otherwise to promote the sale, |
||
| 1290 | use or other dealings in this Software without prior written |
||
| 1291 | authorization. |
||
| 1292 | */ |
||
| 1293 | |||
| 1294 | #ifndef _TEST_DEFAULTS_H_ |
||
| 1295 | #define _TEST_DEFAULTS_H_ |
||
| 1296 | |||
| 1297 | #include "Test.h" |
||
| 1298 | #include "Tester.h" |
||
| 1299 | |||
| 1300 | class TestDefaultProgram : public Test |
||
| 1301 | {
|
||
| 1302 | public: |
||
| 1303 | TestDefaultProgram() : Test() { }
|
||
| 1304 | 8:3019cb6b538d | cannam | Results test(std::string key, Options options); |
| 1305 | 5:6a279da6fdd7 | cannam | |
| 1306 | protected: |
||
| 1307 | static Tester::TestRegistrar<TestDefaultProgram> m_registrar; |
||
| 1308 | }; |
||
| 1309 | |||
| 1310 | class TestDefaultParameters : public Test |
||
| 1311 | {
|
||
| 1312 | public: |
||
| 1313 | TestDefaultParameters() : Test() { }
|
||
| 1314 | 8:3019cb6b538d | cannam | Results test(std::string key, Options options); |
| 1315 | 5:6a279da6fdd7 | cannam | |
| 1316 | protected: |
||
| 1317 | static Tester::TestRegistrar<TestDefaultParameters> m_registrar; |
||
| 1318 | }; |
||
| 1319 | |||
| 1320 | 34:a2d9aed55a2a | Chris | class TestParametersOnReset : public Test |
| 1321 | {
|
||
| 1322 | public: |
||
| 1323 | TestParametersOnReset() : Test() { }
|
||
| 1324 | Results test(std::string key, Options options); |
||
| 1325 | |||
| 1326 | protected: |
||
| 1327 | static Tester::TestRegistrar<TestParametersOnReset> m_registrar; |
||
| 1328 | }; |
||
| 1329 | |||
| 1330 | 5:6a279da6fdd7 | cannam | #endif |
| 1331 | 10:0c1c60654125 | cannam | /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ |
| 1332 | |||
| 1333 | /* |
||
| 1334 | Vamp Plugin Tester |
||
| 1335 | Chris Cannam, cannam@all-day-breakfast.com |
||
| 1336 | Centre for Digital Music, Queen Mary, University of London. |
||
| 1337 | 42:f1e8e14e9c96 | Chris | Copyright 2009-2014 QMUL. |
| 1338 | 10:0c1c60654125 | cannam | |
| 1339 | This program loads a Vamp plugin and tests its susceptibility to a |
||
| 1340 | number of common pitfalls, including handling of extremes of input |
||
| 1341 | data. If you can think of any additional useful tests that are |
||
| 1342 | easily added, please send them to me. |
||
| 1343 | |||
| 1344 | Permission is hereby granted, free of charge, to any person |
||
| 1345 | obtaining a copy of this software and associated documentation |
||
| 1346 | files (the "Software"), to deal in the Software without |
||
| 1347 | restriction, including without limitation the rights to use, copy, |
||
| 1348 | modify, merge, publish, distribute, sublicense, and/or sell copies |
||
| 1349 | of the Software, and to permit persons to whom the Software is |
||
| 1350 | furnished to do so, subject to the following conditions: |
||
| 1351 | |||
| 1352 | The above copyright notice and this permission notice shall be |
||
| 1353 | included in all copies or substantial portions of the Software. |
||
| 1354 | |||
| 1355 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
||
| 1356 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
||
| 1357 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
||
| 1358 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR |
||
| 1359 | ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF |
||
| 1360 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
||
| 1361 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
||
| 1362 | |||
| 1363 | Except as contained in this notice, the names of the Centre for |
||
| 1364 | Digital Music; Queen Mary, University of London; and Chris Cannam |
||
| 1365 | shall not be used in advertising or otherwise to promote the sale, |
||
| 1366 | use or other dealings in this Software without prior written |
||
| 1367 | authorization. |
||
| 1368 | */ |
||
| 1369 | |||
| 1370 | #include "TestInitialise.h" |
||
| 1371 | |||
| 1372 | #include <vamp-hostsdk/Plugin.h> |
||
| 1373 | #include <vamp-hostsdk/PluginLoader.h> |
||
| 1374 | using namespace Vamp; |
||
| 1375 | using namespace Vamp::HostExt; |
||
| 1376 | |||
| 1377 | #include <set> |
||
| 1378 | #include <memory> |
||
| 1379 | using namespace std; |
||
| 1380 | |||
| 1381 | #include <cmath> |
||
| 1382 | 45:56eabc22ccbf | Chris | #include <ctime> |
| 1383 | 10:0c1c60654125 | cannam | |
| 1384 | Tester::TestRegistrar<TestSampleRates> |
||
| 1385 | 39:07144cdcbedf | Chris | TestSampleRates::m_registrar("F1", "Different sample rates");
|
| 1386 | 10:0c1c60654125 | cannam | |
| 1387 | Tester::TestRegistrar<TestLengthyConstructor> |
||
| 1388 | 39:07144cdcbedf | Chris | TestLengthyConstructor::m_registrar("F2", "Lengthy constructor");
|
| 1389 | 10:0c1c60654125 | cannam | |
| 1390 | Test::Results |
||
| 1391 | 23:28097c1b3de4 | cannam | TestSampleRates::test(string key, Options options) |
| 1392 | 10:0c1c60654125 | cannam | {
|
| 1393 | int rates[] = |
||
| 1394 | 43:0973204bf446 | Chris | { 111, 800, 10099, 11024, 44100, 48000, 96000, 192000, 201011, 1094091 };
|
| 1395 | 10:0c1c60654125 | cannam | |
| 1396 | Results r; |
||
| 1397 | |||
| 1398 | 23:28097c1b3de4 | cannam | if (options & Verbose) {
|
| 1399 | cout << " "; |
||
| 1400 | } |
||
| 1401 | |||
| 1402 | 14:e48fdc8de790 | cannam | for (int i = 0; i < int(sizeof(rates)/sizeof(rates[0])); ++i) {
|
| 1403 | 10:0c1c60654125 | cannam | |
| 1404 | int rate = rates[i]; |
||
| 1405 | 23:28097c1b3de4 | cannam | |
| 1406 | if (options & Verbose) {
|
||
| 1407 | cout << "[" << rate << "Hz] " << flush; |
||
| 1408 | } |
||
| 1409 | |||
| 1410 | 10:0c1c60654125 | cannam | auto_ptr<Plugin> p(load(key, rate)); |
| 1411 | Plugin::FeatureSet f; |
||
| 1412 | float **data = 0; |
||
| 1413 | size_t channels = 0; |
||
| 1414 | 43:0973204bf446 | Chris | |
| 1415 | // Aim to feed the plugin a roughly fixed input duration in secs |
||
| 1416 | const float seconds = 10.f; |
||
| 1417 | size_t step = 1000; |
||
| 1418 | size_t count = (seconds * rate) / step; |
||
| 1419 | if (count < 1) count = 1; |
||
| 1420 | 10:0c1c60654125 | cannam | |
| 1421 | 23:28097c1b3de4 | cannam | Results subr; |
| 1422 | 43:0973204bf446 | Chris | if (!initAdapted(p.get(), channels, step, step, subr)) {
|
| 1423 | 23:28097c1b3de4 | cannam | // This is not an error; the plugin can legitimately |
| 1424 | // refuse to initialise at weird settings and that's often |
||
| 1425 | // the most acceptable result |
||
| 1426 | if (!subr.empty()) {
|
||
| 1427 | r.push_back(note(subr.begin()->message())); |
||
| 1428 | } |
||
| 1429 | continue; |
||
| 1430 | } |
||
| 1431 | 10:0c1c60654125 | cannam | |
| 1432 | 43:0973204bf446 | Chris | data = createTestAudio(channels, step, count); |
| 1433 | for (size_t j = 0; j < count; ++j) {
|
||
| 1434 | 67:fa66ee7dcf08 | Chris | float **ptr = new float *[channels]; |
| 1435 | 43:0973204bf446 | Chris | size_t idx = j * step; |
| 1436 | 10:0c1c60654125 | cannam | for (size_t c = 0; c < channels; ++c) ptr[c] = data[c] + idx; |
| 1437 | RealTime timestamp = RealTime::frame2RealTime(idx, rate); |
||
| 1438 | Plugin::FeatureSet fs = p->process(ptr, timestamp); |
||
| 1439 | 67:fa66ee7dcf08 | Chris | delete[] ptr; |
| 1440 | 10:0c1c60654125 | cannam | appendFeatures(f, fs); |
| 1441 | } |
||
| 1442 | Plugin::FeatureSet fs = p->getRemainingFeatures(); |
||
| 1443 | appendFeatures(f, fs); |
||
| 1444 | destroyTestAudio(data, channels); |
||
| 1445 | } |
||
| 1446 | |||
| 1447 | 23:28097c1b3de4 | cannam | if (options & Verbose) cout << endl; |
| 1448 | |||
| 1449 | 10:0c1c60654125 | cannam | // We can't actually do anything meaningful with our results. |
| 1450 | // We're really just testing to see whether the plugin crashes. I |
||
| 1451 | // wonder whether it's possible to do any better? If not, we |
||
| 1452 | // should probably report our limitations |
||
| 1453 | |||
| 1454 | return r; |
||
| 1455 | } |
||
| 1456 | |||
| 1457 | Test::Results |
||
| 1458 | 14:e48fdc8de790 | cannam | TestLengthyConstructor::test(string key, Options) |
| 1459 | 10:0c1c60654125 | cannam | {
|
| 1460 | time_t t0 = time(0); |
||
| 1461 | auto_ptr<Plugin> p(load(key)); |
||
| 1462 | time_t t1 = time(0); |
||
| 1463 | Results r; |
||
| 1464 | if (t1 - t0 > 1) r.push_back(warning("Constructor takes some time to run: work should be deferred to initialise?"));
|
||
| 1465 | return r; |
||
| 1466 | } |
||
| 1467 | |||
| 1468 | /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ |
||
| 1469 | |||
| 1470 | /* |
||
| 1471 | Vamp Plugin Tester |
||
| 1472 | Chris Cannam, cannam@all-day-breakfast.com |
||
| 1473 | Centre for Digital Music, Queen Mary, University of London. |
||
| 1474 | 42:f1e8e14e9c96 | Chris | Copyright 2009-2014 QMUL. |
| 1475 | 10:0c1c60654125 | cannam | |
| 1476 | This program loads a Vamp plugin and tests its susceptibility to a |
||
| 1477 | number of common pitfalls, including handling of extremes of input |
||
| 1478 | data. If you can think of any additional useful tests that are |
||
| 1479 | easily added, please send them to me. |
||
| 1480 | |||
| 1481 | Permission is hereby granted, free of charge, to any person |
||
| 1482 | obtaining a copy of this software and associated documentation |
||
| 1483 | files (the "Software"), to deal in the Software without |
||
| 1484 | restriction, including without limitation the rights to use, copy, |
||
| 1485 | modify, merge, publish, distribute, sublicense, and/or sell copies |
||
| 1486 | of the Software, and to permit persons to whom the Software is |
||
| 1487 | furnished to do so, subject to the following conditions: |
||
| 1488 | |||
| 1489 | The above copyright notice and this permission notice shall be |
||
| 1490 | included in all copies or substantial portions of the Software. |
||
| 1491 | |||
| 1492 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
||
| 1493 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
||
| 1494 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
||
| 1495 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR |
||
| 1496 | ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF |
||
| 1497 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
||
| 1498 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
||
| 1499 | |||
| 1500 | Except as contained in this notice, the names of the Centre for |
||
| 1501 | Digital Music; Queen Mary, University of London; and Chris Cannam |
||
| 1502 | shall not be used in advertising or otherwise to promote the sale, |
||
| 1503 | use or other dealings in this Software without prior written |
||
| 1504 | authorization. |
||
| 1505 | */ |
||
| 1506 | |||
| 1507 | #ifndef _TEST_INITIALISE_H_ |
||
| 1508 | #define _TEST_INITIALISE_H_ |
||
| 1509 | |||
| 1510 | #include "Test.h" |
||
| 1511 | #include "Tester.h" |
||
| 1512 | |||
| 1513 | class TestSampleRates : public Test |
||
| 1514 | {
|
||
| 1515 | public: |
||
| 1516 | TestSampleRates() : Test() { }
|
||
| 1517 | Results test(std::string key, Options options); |
||
| 1518 | |||
| 1519 | protected: |
||
| 1520 | static Tester::TestRegistrar<TestSampleRates> m_registrar; |
||
| 1521 | }; |
||
| 1522 | |||
| 1523 | class TestLengthyConstructor : public Test |
||
| 1524 | {
|
||
| 1525 | public: |
||
| 1526 | TestLengthyConstructor() : Test() { }
|
||
| 1527 | Results test(std::string key, Options options); |
||
| 1528 | |||
| 1529 | protected: |
||
| 1530 | static Tester::TestRegistrar<TestLengthyConstructor> m_registrar; |
||
| 1531 | }; |
||
| 1532 | |||
| 1533 | #endif |
||
| 1534 | 1:d7ef749300ed | cannam | /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ |
| 1535 | |||
| 1536 | /* |
||
| 1537 | Vamp Plugin Tester |
||
| 1538 | Chris Cannam, cannam@all-day-breakfast.com |
||
| 1539 | Centre for Digital Music, Queen Mary, University of London. |
||
| 1540 | 42:f1e8e14e9c96 | Chris | Copyright 2009-2014 QMUL. |
| 1541 | 1:d7ef749300ed | cannam | |
| 1542 | This program loads a Vamp plugin and tests its susceptibility to a |
||
| 1543 | number of common pitfalls, including handling of extremes of input |
||
| 1544 | data. If you can think of any additional useful tests that are |
||
| 1545 | easily added, please send them to me. |
||
| 1546 | |||
| 1547 | Permission is hereby granted, free of charge, to any person |
||
| 1548 | obtaining a copy of this software and associated documentation |
||
| 1549 | files (the "Software"), to deal in the Software without |
||
| 1550 | restriction, including without limitation the rights to use, copy, |
||
| 1551 | modify, merge, publish, distribute, sublicense, and/or sell copies |
||
| 1552 | of the Software, and to permit persons to whom the Software is |
||
| 1553 | furnished to do so, subject to the following conditions: |
||
| 1554 | |||
| 1555 | The above copyright notice and this permission notice shall be |
||
| 1556 | included in all copies or substantial portions of the Software. |
||
| 1557 | |||
| 1558 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
||
| 1559 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
||
| 1560 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
||
| 1561 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR |
||
| 1562 | ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF |
||
| 1563 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
||
| 1564 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
||
| 1565 | |||
| 1566 | Except as contained in this notice, the names of the Centre for |
||
| 1567 | Digital Music; Queen Mary, University of London; and Chris Cannam |
||
| 1568 | shall not be used in advertising or otherwise to promote the sale, |
||
| 1569 | use or other dealings in this Software without prior written |
||
| 1570 | authorization. |
||
| 1571 | */ |
||
| 1572 | |||
| 1573 | #include "TestInputExtremes.h" |
||
| 1574 | |||
| 1575 | #include <vamp-hostsdk/Plugin.h> |
||
| 1576 | using namespace Vamp; |
||
| 1577 | |||
| 1578 | #include <memory> |
||
| 1579 | using namespace std; |
||
| 1580 | |||
| 1581 | 2:c9a4bd247497 | cannam | #include <cstdlib> |
| 1582 | 1:d7ef749300ed | cannam | #include <cmath> |
| 1583 | |||
| 1584 | Tester::TestRegistrar<TestNormalInput> |
||
| 1585 | 39:07144cdcbedf | Chris | TestNormalInput::m_registrar("C1", "Normal input");
|
| 1586 | 1:d7ef749300ed | cannam | |
| 1587 | Tester::TestRegistrar<TestNoInput> |
||
| 1588 | 39:07144cdcbedf | Chris | TestNoInput::m_registrar("C2", "Empty input");
|
| 1589 | 1:d7ef749300ed | cannam | |
| 1590 | Tester::TestRegistrar<TestShortInput> |
||
| 1591 | 39:07144cdcbedf | Chris | TestShortInput::m_registrar("C3", "Short input");
|
| 1592 | 1:d7ef749300ed | cannam | |
| 1593 | Tester::TestRegistrar<TestSilentInput> |
||
| 1594 | 39:07144cdcbedf | Chris | TestSilentInput::m_registrar("C4", "Absolutely silent input");
|
| 1595 | 1:d7ef749300ed | cannam | |
| 1596 | Tester::TestRegistrar<TestTooLoudInput> |
||
| 1597 | 39:07144cdcbedf | Chris | TestTooLoudInput::m_registrar("C5", "Input beyond traditional +/-1 range");
|
| 1598 | 1:d7ef749300ed | cannam | |
| 1599 | Tester::TestRegistrar<TestRandomInput> |
||
| 1600 | 39:07144cdcbedf | Chris | TestRandomInput::m_registrar("C6", "Random input");
|
| 1601 | 1:d7ef749300ed | cannam | |
| 1602 | Test::Results |
||
| 1603 | 8:3019cb6b538d | cannam | TestNormalInput::test(string key, Options options) |
| 1604 | 1:d7ef749300ed | cannam | {
|
| 1605 | 2:c9a4bd247497 | cannam | Plugin::FeatureSet f; |
| 1606 | 1:d7ef749300ed | cannam | int rate = 44100; |
| 1607 | auto_ptr<Plugin> p(load(key, rate)); |
||
| 1608 | Results r; |
||
| 1609 | size_t channels, step, blocksize; |
||
| 1610 | if (!initDefaults(p.get(), channels, step, blocksize, r)) return r; |
||
| 1611 | float **block = createBlock(channels, blocksize); |
||
| 1612 | int idx = 0; |
||
| 1613 | for (int i = 0; i < 200; ++i) {
|
||
| 1614 | for (size_t j = 0; j < blocksize; ++j) {
|
||
| 1615 | for (size_t c = 0; c < channels; ++c) {
|
||
| 1616 | block[c][j] = sinf(float(idx) / 10.f); |
||
| 1617 | } |
||
| 1618 | ++idx; |
||
| 1619 | } |
||
| 1620 | RealTime timestamp = RealTime::frame2RealTime(idx, rate); |
||
| 1621 | 2:c9a4bd247497 | cannam | Plugin::FeatureSet fs = p->process(block, timestamp); |
| 1622 | appendFeatures(f, fs); |
||
| 1623 | 1:d7ef749300ed | cannam | } |
| 1624 | destroyBlock(block, channels); |
||
| 1625 | Plugin::FeatureSet fs = p->getRemainingFeatures(); |
||
| 1626 | 2:c9a4bd247497 | cannam | appendFeatures(f, fs); |
| 1627 | if (allFeaturesValid(f)) {
|
||
| 1628 | 1:d7ef749300ed | cannam | r.push_back(success()); |
| 1629 | } else {
|
||
| 1630 | 17:ea8865f488a0 | cannam | r.push_back(warning("Plugin returned one or more NaN/inf values"));
|
| 1631 | 8:3019cb6b538d | cannam | if (options & Verbose) dump(f); |
| 1632 | 1:d7ef749300ed | cannam | } |
| 1633 | return r; |
||
| 1634 | } |
||
| 1635 | |||
| 1636 | Test::Results |
||
| 1637 | 16:419d538874a8 | cannam | TestNoInput::test(string key, Options) |
| 1638 | 1:d7ef749300ed | cannam | {
|
| 1639 | auto_ptr<Plugin> p(load(key)); |
||
| 1640 | Results r; |
||
| 1641 | size_t channels, step, block; |
||
| 1642 | if (!initDefaults(p.get(), channels, step, block, r)) return r; |
||
| 1643 | Plugin::FeatureSet fs = p->getRemainingFeatures(); |
||
| 1644 | if (allFeaturesValid(fs)) {
|
||
| 1645 | r.push_back(success()); |
||
| 1646 | } else {
|
||
| 1647 | 17:ea8865f488a0 | cannam | r.push_back(warning("Plugin returned one or more NaN/inf values"));
|
| 1648 | 1:d7ef749300ed | cannam | } |
| 1649 | return r; |
||
| 1650 | } |
||
| 1651 | |||
| 1652 | Test::Results |
||
| 1653 | 8:3019cb6b538d | cannam | TestShortInput::test(string key, Options options) |
| 1654 | 1:d7ef749300ed | cannam | {
|
| 1655 | 2:c9a4bd247497 | cannam | Plugin::FeatureSet f; |
| 1656 | 1:d7ef749300ed | cannam | int rate = 44100; |
| 1657 | auto_ptr<Plugin> p(load(key, rate)); |
||
| 1658 | Results r; |
||
| 1659 | size_t channels, step, blocksize; |
||
| 1660 | if (!initDefaults(p.get(), channels, step, blocksize, r)) return r; |
||
| 1661 | float **block = createBlock(channels, blocksize); |
||
| 1662 | int idx = 0; |
||
| 1663 | for (size_t j = 0; j < blocksize; ++j) {
|
||
| 1664 | for (size_t c = 0; c < channels; ++c) {
|
||
| 1665 | block[c][j] = sinf(float(idx) / 10.f); |
||
| 1666 | } |
||
| 1667 | ++idx; |
||
| 1668 | } |
||
| 1669 | 2:c9a4bd247497 | cannam | Plugin::FeatureSet fs = p->process(block, RealTime::zeroTime); |
| 1670 | appendFeatures(f, fs); |
||
| 1671 | 1:d7ef749300ed | cannam | destroyBlock(block, channels); |
| 1672 | 2:c9a4bd247497 | cannam | fs = p->getRemainingFeatures(); |
| 1673 | appendFeatures(f, fs); |
||
| 1674 | if (allFeaturesValid(f)) {
|
||
| 1675 | 1:d7ef749300ed | cannam | r.push_back(success()); |
| 1676 | } else {
|
||
| 1677 | 17:ea8865f488a0 | cannam | r.push_back(warning("Plugin returned one or more NaN/inf values"));
|
| 1678 | 8:3019cb6b538d | cannam | if (options & Verbose) dump(f); |
| 1679 | 1:d7ef749300ed | cannam | } |
| 1680 | return r; |
||
| 1681 | } |
||
| 1682 | |||
| 1683 | Test::Results |
||
| 1684 | 8:3019cb6b538d | cannam | TestSilentInput::test(string key, Options options) |
| 1685 | 1:d7ef749300ed | cannam | {
|
| 1686 | 2:c9a4bd247497 | cannam | Plugin::FeatureSet f; |
| 1687 | 1:d7ef749300ed | cannam | int rate = 44100; |
| 1688 | auto_ptr<Plugin> p(load(key, rate)); |
||
| 1689 | Results r; |
||
| 1690 | size_t channels, step, blocksize; |
||
| 1691 | if (!initDefaults(p.get(), channels, step, blocksize, r)) return r; |
||
| 1692 | float **block = createBlock(channels, blocksize); |
||
| 1693 | for (size_t j = 0; j < blocksize; ++j) {
|
||
| 1694 | for (size_t c = 0; c < channels; ++c) {
|
||
| 1695 | block[c][j] = 0.f; |
||
| 1696 | } |
||
| 1697 | } |
||
| 1698 | for (int i = 0; i < 200; ++i) {
|
||
| 1699 | RealTime timestamp = RealTime::frame2RealTime(i * blocksize, rate); |
||
| 1700 | 2:c9a4bd247497 | cannam | Plugin::FeatureSet fs = p->process(block, timestamp); |
| 1701 | appendFeatures(f, fs); |
||
| 1702 | 1:d7ef749300ed | cannam | } |
| 1703 | destroyBlock(block, channels); |
||
| 1704 | Plugin::FeatureSet fs = p->getRemainingFeatures(); |
||
| 1705 | 2:c9a4bd247497 | cannam | appendFeatures(f, fs); |
| 1706 | if (allFeaturesValid(f)) {
|
||
| 1707 | 1:d7ef749300ed | cannam | r.push_back(success()); |
| 1708 | } else {
|
||
| 1709 | 17:ea8865f488a0 | cannam | r.push_back(warning("Plugin returned one or more NaN/inf values"));
|
| 1710 | 8:3019cb6b538d | cannam | if (options & Verbose) dump(f); |
| 1711 | 1:d7ef749300ed | cannam | } |
| 1712 | return r; |
||
| 1713 | } |
||
| 1714 | |||
| 1715 | Test::Results |
||
| 1716 | 8:3019cb6b538d | cannam | TestTooLoudInput::test(string key, Options options) |
| 1717 | 1:d7ef749300ed | cannam | {
|
| 1718 | 2:c9a4bd247497 | cannam | Plugin::FeatureSet f; |
| 1719 | 1:d7ef749300ed | cannam | int rate = 44100; |
| 1720 | auto_ptr<Plugin> p(load(key, rate)); |
||
| 1721 | Results r; |
||
| 1722 | size_t channels, step, blocksize; |
||
| 1723 | if (!initDefaults(p.get(), channels, step, blocksize, r)) return r; |
||
| 1724 | float **block = createBlock(channels, blocksize); |
||
| 1725 | int idx = 0; |
||
| 1726 | for (int i = 0; i < 200; ++i) {
|
||
| 1727 | for (size_t j = 0; j < blocksize; ++j) {
|
||
| 1728 | for (size_t c = 0; c < channels; ++c) {
|
||
| 1729 | block[c][j] = 1000.f * sinf(float(idx) / 10.f); |
||
| 1730 | } |
||
| 1731 | ++idx; |
||
| 1732 | } |
||
| 1733 | RealTime timestamp = RealTime::frame2RealTime(idx, rate); |
||
| 1734 | 2:c9a4bd247497 | cannam | Plugin::FeatureSet fs = p->process(block, timestamp); |
| 1735 | appendFeatures(f, fs); |
||
| 1736 | 1:d7ef749300ed | cannam | } |
| 1737 | destroyBlock(block, channels); |
||
| 1738 | Plugin::FeatureSet fs = p->getRemainingFeatures(); |
||
| 1739 | 2:c9a4bd247497 | cannam | appendFeatures(f, fs); |
| 1740 | if (allFeaturesValid(f)) {
|
||
| 1741 | 1:d7ef749300ed | cannam | r.push_back(success()); |
| 1742 | } else {
|
||
| 1743 | 17:ea8865f488a0 | cannam | r.push_back(warning("Plugin returned one or more NaN/inf values"));
|
| 1744 | 8:3019cb6b538d | cannam | if (options & Verbose) dump(f); |
| 1745 | 1:d7ef749300ed | cannam | } |
| 1746 | return r; |
||
| 1747 | } |
||
| 1748 | |||
| 1749 | Test::Results |
||
| 1750 | 8:3019cb6b538d | cannam | TestRandomInput::test(string key, Options options) |
| 1751 | 1:d7ef749300ed | cannam | {
|
| 1752 | 2:c9a4bd247497 | cannam | Plugin::FeatureSet f; |
| 1753 | 1:d7ef749300ed | cannam | int rate = 44100; |
| 1754 | auto_ptr<Plugin> p(load(key, rate)); |
||
| 1755 | Results r; |
||
| 1756 | size_t channels, step, blocksize; |
||
| 1757 | if (!initDefaults(p.get(), channels, step, blocksize, r)) return r; |
||
| 1758 | float **block = createBlock(channels, blocksize); |
||
| 1759 | int idx = 0; |
||
| 1760 | for (int i = 0; i < 100; ++i) {
|
||
| 1761 | for (size_t j = 0; j < blocksize; ++j) {
|
||
| 1762 | for (size_t c = 0; c < channels; ++c) {
|
||
| 1763 | 21:019f6415950d | cannam | block[c][j] = (float(rand()) / RAND_MAX) * 2.0 - 1.0; |
| 1764 | 1:d7ef749300ed | cannam | } |
| 1765 | ++idx; |
||
| 1766 | } |
||
| 1767 | RealTime timestamp = RealTime::frame2RealTime(idx, rate); |
||
| 1768 | 2:c9a4bd247497 | cannam | Plugin::FeatureSet fs = p->process(block, timestamp); |
| 1769 | appendFeatures(f, fs); |
||
| 1770 | 1:d7ef749300ed | cannam | } |
| 1771 | destroyBlock(block, channels); |
||
| 1772 | Plugin::FeatureSet fs = p->getRemainingFeatures(); |
||
| 1773 | 2:c9a4bd247497 | cannam | appendFeatures(f, fs); |
| 1774 | if (allFeaturesValid(f)) {
|
||
| 1775 | 1:d7ef749300ed | cannam | r.push_back(success()); |
| 1776 | } else {
|
||
| 1777 | 17:ea8865f488a0 | cannam | r.push_back(warning("Plugin returned one or more NaN/inf values"));
|
| 1778 | 8:3019cb6b538d | cannam | if (options & Verbose) dump(f); |
| 1779 | 1:d7ef749300ed | cannam | } |
| 1780 | return r; |
||
| 1781 | } |
||
| 1782 | |||
| 1783 | /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ |
||
| 1784 | |||
| 1785 | /* |
||
| 1786 | Vamp Plugin Tester |
||
| 1787 | Chris Cannam, cannam@all-day-breakfast.com |
||
| 1788 | Centre for Digital Music, Queen Mary, University of London. |
||
| 1789 | 42:f1e8e14e9c96 | Chris | Copyright 2009-2014 QMUL. |
| 1790 | 1:d7ef749300ed | cannam | |
| 1791 | This program loads a Vamp plugin and tests its susceptibility to a |
||
| 1792 | number of common pitfalls, including handling of extremes of input |
||
| 1793 | data. If you can think of any additional useful tests that are |
||
| 1794 | easily added, please send them to me. |
||
| 1795 | |||
| 1796 | Permission is hereby granted, free of charge, to any person |
||
| 1797 | obtaining a copy of this software and associated documentation |
||
| 1798 | files (the "Software"), to deal in the Software without |
||
| 1799 | restriction, including without limitation the rights to use, copy, |
||
| 1800 | modify, merge, publish, distribute, sublicense, and/or sell copies |
||
| 1801 | of the Software, and to permit persons to whom the Software is |
||
| 1802 | furnished to do so, subject to the following conditions: |
||
| 1803 | |||
| 1804 | The above copyright notice and this permission notice shall be |
||
| 1805 | included in all copies or substantial portions of the Software. |
||
| 1806 | |||
| 1807 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
||
| 1808 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
||
| 1809 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
||
| 1810 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR |
||
| 1811 | ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF |
||
| 1812 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
||
| 1813 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
||
| 1814 | |||
| 1815 | Except as contained in this notice, the names of the Centre for |
||
| 1816 | Digital Music; Queen Mary, University of London; and Chris Cannam |
||
| 1817 | shall not be used in advertising or otherwise to promote the sale, |
||
| 1818 | use or other dealings in this Software without prior written |
||
| 1819 | authorization. |
||
| 1820 | */ |
||
| 1821 | |||
| 1822 | #ifndef _TEST_INPUT_EXTREMES_H_ |
||
| 1823 | #define _TEST_INPUT_EXTREMES_H_ |
||
| 1824 | |||
| 1825 | #include "Test.h" |
||
| 1826 | #include "Tester.h" |
||
| 1827 | |||
| 1828 | #include <string> |
||
| 1829 | |||
| 1830 | #include <vamp-hostsdk/Plugin.h> |
||
| 1831 | |||
| 1832 | class TestNormalInput : public Test |
||
| 1833 | {
|
||
| 1834 | public: |
||
| 1835 | TestNormalInput() : Test() { }
|
||
| 1836 | 8:3019cb6b538d | cannam | Results test(std::string key, Options options); |
| 1837 | 1:d7ef749300ed | cannam | |
| 1838 | protected: |
||
| 1839 | static Tester::TestRegistrar<TestNormalInput> m_registrar; |
||
| 1840 | }; |
||
| 1841 | |||
| 1842 | class TestNoInput : public Test |
||
| 1843 | {
|
||
| 1844 | public: |
||
| 1845 | TestNoInput() : Test() { }
|
||
| 1846 | 8:3019cb6b538d | cannam | Results test(std::string key, Options options); |
| 1847 | 1:d7ef749300ed | cannam | |
| 1848 | protected: |
||
| 1849 | static Tester::TestRegistrar<TestNoInput> m_registrar; |
||
| 1850 | }; |
||
| 1851 | |||
| 1852 | class TestShortInput : public Test |
||
| 1853 | {
|
||
| 1854 | public: |
||
| 1855 | TestShortInput() : Test() { }
|
||
| 1856 | 8:3019cb6b538d | cannam | Results test(std::string key, Options options); |
| 1857 | 1:d7ef749300ed | cannam | |
| 1858 | protected: |
||
| 1859 | static Tester::TestRegistrar<TestShortInput> m_registrar; |
||
| 1860 | }; |
||
| 1861 | |||
| 1862 | class TestSilentInput : public Test |
||
| 1863 | {
|
||
| 1864 | public: |
||
| 1865 | TestSilentInput() : Test() { }
|
||
| 1866 | 8:3019cb6b538d | cannam | Results test(std::string key, Options options); |
| 1867 | 1:d7ef749300ed | cannam | |
| 1868 | protected: |
||
| 1869 | static Tester::TestRegistrar<TestSilentInput> m_registrar; |
||
| 1870 | }; |
||
| 1871 | |||
| 1872 | class TestTooLoudInput : public Test |
||
| 1873 | {
|
||
| 1874 | public: |
||
| 1875 | TestTooLoudInput() : Test() { }
|
||
| 1876 | 8:3019cb6b538d | cannam | Results test(std::string key, Options options); |
| 1877 | 1:d7ef749300ed | cannam | |
| 1878 | protected: |
||
| 1879 | static Tester::TestRegistrar<TestTooLoudInput> m_registrar; |
||
| 1880 | }; |
||
| 1881 | |||
| 1882 | class TestRandomInput : public Test |
||
| 1883 | {
|
||
| 1884 | public: |
||
| 1885 | TestRandomInput() : Test() { }
|
||
| 1886 | 8:3019cb6b538d | cannam | Results test(std::string key, Options options); |
| 1887 | 1:d7ef749300ed | cannam | |
| 1888 | protected: |
||
| 1889 | static Tester::TestRegistrar<TestRandomInput> m_registrar; |
||
| 1890 | }; |
||
| 1891 | |||
| 1892 | |||
| 1893 | #endif |
||
| 1894 | |||
| 1895 | 2:c9a4bd247497 | cannam | /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ |
| 1896 | |||
| 1897 | /* |
||
| 1898 | Vamp Plugin Tester |
||
| 1899 | Chris Cannam, cannam@all-day-breakfast.com |
||
| 1900 | Centre for Digital Music, Queen Mary, University of London. |
||
| 1901 | 42:f1e8e14e9c96 | Chris | Copyright 2009-2014 QMUL. |
| 1902 | 2:c9a4bd247497 | cannam | |
| 1903 | This program loads a Vamp plugin and tests its susceptibility to a |
||
| 1904 | number of common pitfalls, including handling of extremes of input |
||
| 1905 | data. If you can think of any additional useful tests that are |
||
| 1906 | easily added, please send them to me. |
||
| 1907 | |||
| 1908 | Permission is hereby granted, free of charge, to any person |
||
| 1909 | obtaining a copy of this software and associated documentation |
||
| 1910 | files (the "Software"), to deal in the Software without |
||
| 1911 | restriction, including without limitation the rights to use, copy, |
||
| 1912 | modify, merge, publish, distribute, sublicense, and/or sell copies |
||
| 1913 | of the Software, and to permit persons to whom the Software is |
||
| 1914 | furnished to do so, subject to the following conditions: |
||
| 1915 | |||
| 1916 | The above copyright notice and this permission notice shall be |
||
| 1917 | included in all copies or substantial portions of the Software. |
||
| 1918 | |||
| 1919 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
||
| 1920 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
||
| 1921 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
||
| 1922 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR |
||
| 1923 | ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF |
||
| 1924 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
||
| 1925 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
||
| 1926 | |||
| 1927 | Except as contained in this notice, the names of the Centre for |
||
| 1928 | Digital Music; Queen Mary, University of London; and Chris Cannam |
||
| 1929 | shall not be used in advertising or otherwise to promote the sale, |
||
| 1930 | use or other dealings in this Software without prior written |
||
| 1931 | authorization. |
||
| 1932 | */ |
||
| 1933 | |||
| 1934 | #include "TestMultipleRuns.h" |
||
| 1935 | |||
| 1936 | #include <vamp-hostsdk/Plugin.h> |
||
| 1937 | using namespace Vamp; |
||
| 1938 | |||
| 1939 | #include <memory> |
||
| 1940 | using namespace std; |
||
| 1941 | |||
| 1942 | #include <cmath> |
||
| 1943 | |||
| 1944 | Tester::TestRegistrar<TestDistinctRuns> |
||
| 1945 | 39:07144cdcbedf | Chris | TestDistinctRuns::m_registrar("D1", "Consecutive runs with separate instances");
|
| 1946 | 2:c9a4bd247497 | cannam | |
| 1947 | Tester::TestRegistrar<TestReset> |
||
| 1948 | 39:07144cdcbedf | Chris | TestReset::m_registrar("D2", "Consecutive runs with a single instance using reset");
|
| 1949 | 2:c9a4bd247497 | cannam | |
| 1950 | Tester::TestRegistrar<TestInterleavedRuns> |
||
| 1951 | 39:07144cdcbedf | Chris | TestInterleavedRuns::m_registrar("D3", "Simultaneous interleaved runs in a single thread");
|
| 1952 | 2:c9a4bd247497 | cannam | |
| 1953 | 5:6a279da6fdd7 | cannam | Tester::TestRegistrar<TestDifferentStartTimes> |
| 1954 | 39:07144cdcbedf | Chris | TestDifferentStartTimes::m_registrar("D4", "Consecutive runs with different start times");
|
| 1955 | 5:6a279da6fdd7 | cannam | |
| 1956 | 3:0f65bb22172b | cannam | static const size_t _step = 1000; |
| 1957 | |||
| 1958 | 2:c9a4bd247497 | cannam | Test::Results |
| 1959 | 8:3019cb6b538d | cannam | TestDistinctRuns::test(string key, Options options) |
| 1960 | 2:c9a4bd247497 | cannam | {
|
| 1961 | Plugin::FeatureSet f[2]; |
||
| 1962 | int rate = 44100; |
||
| 1963 | Results r; |
||
| 1964 | 3:0f65bb22172b | cannam | float **data = 0; |
| 1965 | size_t channels = 0; |
||
| 1966 | size_t count = 100; |
||
| 1967 | 2:c9a4bd247497 | cannam | |
| 1968 | for (int run = 0; run < 2; ++run) {
|
||
| 1969 | auto_ptr<Plugin> p(load(key, rate)); |
||
| 1970 | 3:0f65bb22172b | cannam | if (!initAdapted(p.get(), channels, _step, _step, r)) return r; |
| 1971 | if (!data) data = createTestAudio(channels, _step, count); |
||
| 1972 | for (size_t i = 0; i < count; ++i) {
|
||
| 1973 | 67:fa66ee7dcf08 | Chris | float **ptr = new float *[channels]; |
| 1974 | 3:0f65bb22172b | cannam | size_t idx = i * _step; |
| 1975 | for (size_t c = 0; c < channels; ++c) ptr[c] = data[c] + idx; |
||
| 1976 | 2:c9a4bd247497 | cannam | RealTime timestamp = RealTime::frame2RealTime(idx, rate); |
| 1977 | 3:0f65bb22172b | cannam | Plugin::FeatureSet fs = p->process(ptr, timestamp); |
| 1978 | 67:fa66ee7dcf08 | Chris | delete[] ptr; |
| 1979 | 2:c9a4bd247497 | cannam | appendFeatures(f[run], fs); |
| 1980 | } |
||
| 1981 | Plugin::FeatureSet fs = p->getRemainingFeatures(); |
||
| 1982 | appendFeatures(f[run], fs); |
||
| 1983 | } |
||
| 1984 | 3:0f65bb22172b | cannam | if (data) destroyTestAudio(data, channels); |
| 1985 | 2:c9a4bd247497 | cannam | |
| 1986 | if (!(f[0] == f[1])) {
|
||
| 1987 | 8:3019cb6b538d | cannam | Result res; |
| 1988 | string message = "Consecutive runs with separate instances produce different results"; |
||
| 1989 | if (options & NonDeterministic) res = note(message); |
||
| 1990 | else res = error(message); |
||
| 1991 | 52:4bd0cd3c60f3 | Chris | if (options & Verbose) dumpDiff(res, f[0], f[1]); |
| 1992 | 7:43eb3a4b95c8 | cannam | r.push_back(res); |
| 1993 | 2:c9a4bd247497 | cannam | } else {
|
| 1994 | r.push_back(success()); |
||
| 1995 | } |
||
| 1996 | |||
| 1997 | return r; |
||
| 1998 | } |
||
| 1999 | |||
| 2000 | Test::Results |
||
| 2001 | 8:3019cb6b538d | cannam | TestReset::test(string key, Options options) |
| 2002 | 2:c9a4bd247497 | cannam | {
|
| 2003 | Plugin::FeatureSet f[2]; |
||
| 2004 | int rate = 44100; |
||
| 2005 | Results r; |
||
| 2006 | 3:0f65bb22172b | cannam | float **data = 0; |
| 2007 | size_t channels = 0; |
||
| 2008 | size_t count = 100; |
||
| 2009 | 2:c9a4bd247497 | cannam | |
| 2010 | auto_ptr<Plugin> p(load(key, rate)); |
||
| 2011 | 3:0f65bb22172b | cannam | |
| 2012 | 2:c9a4bd247497 | cannam | for (int run = 0; run < 2; ++run) {
|
| 2013 | if (run == 1) p->reset(); |
||
| 2014 | 11:82ef943a1bd2 | cannam | else if (!initAdapted(p.get(), channels, _step, _step, r)) return r; |
| 2015 | 3:0f65bb22172b | cannam | if (!data) data = createTestAudio(channels, _step, count); |
| 2016 | for (size_t i = 0; i < count; ++i) {
|
||
| 2017 | 67:fa66ee7dcf08 | Chris | float **ptr = new float *[channels]; |
| 2018 | 3:0f65bb22172b | cannam | size_t idx = i * _step; |
| 2019 | for (size_t c = 0; c < channels; ++c) ptr[c] = data[c] + idx; |
||
| 2020 | 2:c9a4bd247497 | cannam | RealTime timestamp = RealTime::frame2RealTime(idx, rate); |
| 2021 | 3:0f65bb22172b | cannam | Plugin::FeatureSet fs = p->process(ptr, timestamp); |
| 2022 | 67:fa66ee7dcf08 | Chris | delete[] ptr; |
| 2023 | 2:c9a4bd247497 | cannam | appendFeatures(f[run], fs); |
| 2024 | } |
||
| 2025 | Plugin::FeatureSet fs = p->getRemainingFeatures(); |
||
| 2026 | appendFeatures(f[run], fs); |
||
| 2027 | } |
||
| 2028 | 3:0f65bb22172b | cannam | if (data) destroyTestAudio(data, channels); |
| 2029 | 2:c9a4bd247497 | cannam | |
| 2030 | if (!(f[0] == f[1])) {
|
||
| 2031 | 8:3019cb6b538d | cannam | string message = "Consecutive runs with the same instance (using reset) produce different results"; |
| 2032 | Result res; |
||
| 2033 | if (options & NonDeterministic) res = note(message); |
||
| 2034 | else res = error(message); |
||
| 2035 | 52:4bd0cd3c60f3 | Chris | if (options & Verbose) dumpDiff(res, f[0], f[1]); |
| 2036 | 3:0f65bb22172b | cannam | r.push_back(res); |
| 2037 | 2:c9a4bd247497 | cannam | } else {
|
| 2038 | r.push_back(success()); |
||
| 2039 | } |
||
| 2040 | |||
| 2041 | return r; |
||
| 2042 | } |
||
| 2043 | |||
| 2044 | Test::Results |
||
| 2045 | 8:3019cb6b538d | cannam | TestInterleavedRuns::test(string key, Options options) |
| 2046 | 2:c9a4bd247497 | cannam | {
|
| 2047 | Plugin::FeatureSet f[2]; |
||
| 2048 | int rate = 44100; |
||
| 2049 | Results r; |
||
| 2050 | 3:0f65bb22172b | cannam | float **data = 0; |
| 2051 | size_t channels = 0; |
||
| 2052 | size_t count = 100; |
||
| 2053 | |||
| 2054 | 2:c9a4bd247497 | cannam | Plugin *p[2]; |
| 2055 | for (int run = 0; run < 2; ++run) {
|
||
| 2056 | p[run] = load(key, rate); |
||
| 2057 | 3:0f65bb22172b | cannam | if (!initAdapted(p[run], channels, _step, _step, r)) {
|
| 2058 | 2:c9a4bd247497 | cannam | delete p[run]; |
| 2059 | if (run > 0) delete p[0]; |
||
| 2060 | return r; |
||
| 2061 | } |
||
| 2062 | 3:0f65bb22172b | cannam | if (!data) data = createTestAudio(channels, _step, count); |
| 2063 | 2:c9a4bd247497 | cannam | } |
| 2064 | 3:0f65bb22172b | cannam | for (size_t i = 0; i < count; ++i) {
|
| 2065 | 67:fa66ee7dcf08 | Chris | float **ptr = new float *[channels]; |
| 2066 | 3:0f65bb22172b | cannam | size_t idx = i * _step; |
| 2067 | for (size_t c = 0; c < channels; ++c) ptr[c] = data[c] + idx; |
||
| 2068 | 2:c9a4bd247497 | cannam | RealTime timestamp = RealTime::frame2RealTime(idx, rate); |
| 2069 | for (int run = 0; run < 2; ++run) {
|
||
| 2070 | 3:0f65bb22172b | cannam | Plugin::FeatureSet fs = p[run]->process(ptr, timestamp); |
| 2071 | 2:c9a4bd247497 | cannam | appendFeatures(f[run], fs); |
| 2072 | } |
||
| 2073 | 67:fa66ee7dcf08 | Chris | delete[] ptr; |
| 2074 | 2:c9a4bd247497 | cannam | } |
| 2075 | for (int run = 0; run < 2; ++run) {
|
||
| 2076 | Plugin::FeatureSet fs = p[run]->getRemainingFeatures(); |
||
| 2077 | appendFeatures(f[run], fs); |
||
| 2078 | delete p[run]; |
||
| 2079 | } |
||
| 2080 | |||
| 2081 | 3:0f65bb22172b | cannam | if (data) destroyTestAudio(data, channels); |
| 2082 | 2:c9a4bd247497 | cannam | |
| 2083 | if (!(f[0] == f[1])) {
|
||
| 2084 | 8:3019cb6b538d | cannam | string message = "Simultaneous runs with separate instances produce different results"; |
| 2085 | Result res; |
||
| 2086 | if (options & NonDeterministic) res = note(message); |
||
| 2087 | else res = error(message); |
||
| 2088 | 52:4bd0cd3c60f3 | Chris | if (options & Verbose) dumpDiff(res, f[0], f[1]); |
| 2089 | 7:43eb3a4b95c8 | cannam | r.push_back(res); |
| 2090 | 2:c9a4bd247497 | cannam | } else {
|
| 2091 | r.push_back(success()); |
||
| 2092 | } |
||
| 2093 | |||
| 2094 | return r; |
||
| 2095 | } |
||
| 2096 | 5:6a279da6fdd7 | cannam | |
| 2097 | Test::Results |
||
| 2098 | 8:3019cb6b538d | cannam | TestDifferentStartTimes::test(string key, Options options) |
| 2099 | 5:6a279da6fdd7 | cannam | {
|
| 2100 | Plugin::FeatureSet f[2]; |
||
| 2101 | int rate = 44100; |
||
| 2102 | Results r; |
||
| 2103 | float **data = 0; |
||
| 2104 | size_t channels = 0; |
||
| 2105 | size_t count = 100; |
||
| 2106 | |||
| 2107 | for (int run = 0; run < 2; ++run) {
|
||
| 2108 | auto_ptr<Plugin> p(load(key, rate)); |
||
| 2109 | if (!initAdapted(p.get(), channels, _step, _step, r)) return r; |
||
| 2110 | if (!data) data = createTestAudio(channels, _step, count); |
||
| 2111 | for (size_t i = 0; i < count; ++i) {
|
||
| 2112 | 67:fa66ee7dcf08 | Chris | float **ptr = new float *[channels]; |
| 2113 | 5:6a279da6fdd7 | cannam | size_t idx = i * _step; |
| 2114 | for (size_t c = 0; c < channels; ++c) ptr[c] = data[c] + idx; |
||
| 2115 | RealTime timestamp = RealTime::frame2RealTime(idx, rate); |
||
| 2116 | if (run == 1) timestamp = timestamp + RealTime::fromSeconds(10); |
||
| 2117 | Plugin::FeatureSet fs = p->process(ptr, timestamp); |
||
| 2118 | 67:fa66ee7dcf08 | Chris | delete[] ptr; |
| 2119 | 5:6a279da6fdd7 | cannam | appendFeatures(f[run], fs); |
| 2120 | } |
||
| 2121 | Plugin::FeatureSet fs = p->getRemainingFeatures(); |
||
| 2122 | appendFeatures(f[run], fs); |
||
| 2123 | } |
||
| 2124 | if (data) destroyTestAudio(data, channels); |
||
| 2125 | |||
| 2126 | if (f[0] == f[1]) {
|
||
| 2127 | 8:3019cb6b538d | cannam | Result res; |
| 2128 | 53:86d8a699dfbe | Chris | if (containsTimestamps(f[0])) {
|
| 2129 | string message = "Consecutive runs with different starting timestamps produce the same result"; |
||
| 2130 | if (options & NonDeterministic) {
|
||
| 2131 | res = note(message); |
||
| 2132 | } else {
|
||
| 2133 | res = warning(message); |
||
| 2134 | } |
||
| 2135 | if (options & Verbose) {
|
||
| 2136 | cout << res.message() << endl; |
||
| 2137 | dump(f[0], false); |
||
| 2138 | } |
||
| 2139 | } else {
|
||
| 2140 | res = note("Consecutive runs with different starting timestamps produce the same result (but result features contain no timestamps, so this is probably all right)");
|
||
| 2141 | 52:4bd0cd3c60f3 | Chris | } |
| 2142 | 7:43eb3a4b95c8 | cannam | r.push_back(res); |
| 2143 | 5:6a279da6fdd7 | cannam | } else {
|
| 2144 | r.push_back(success()); |
||
| 2145 | } |
||
| 2146 | |||
| 2147 | return r; |
||
| 2148 | } |
||
| 2149 | 2:c9a4bd247497 | cannam | /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ |
| 2150 | |||
| 2151 | /* |
||
| 2152 | Vamp Plugin Tester |
||
| 2153 | Chris Cannam, cannam@all-day-breakfast.com |
||
| 2154 | Centre for Digital Music, Queen Mary, University of London. |
||
| 2155 | 42:f1e8e14e9c96 | Chris | Copyright 2009-2014 QMUL. |
| 2156 | 2:c9a4bd247497 | cannam | |
| 2157 | This program loads a Vamp plugin and tests its susceptibility to a |
||
| 2158 | number of common pitfalls, including handling of extremes of input |
||
| 2159 | data. If you can think of any additional useful tests that are |
||
| 2160 | easily added, please send them to me. |
||
| 2161 | |||
| 2162 | Permission is hereby granted, free of charge, to any person |
||
| 2163 | obtaining a copy of this software and associated documentation |
||
| 2164 | files (the "Software"), to deal in the Software without |
||
| 2165 | restriction, including without limitation the rights to use, copy, |
||
| 2166 | modify, merge, publish, distribute, sublicense, and/or sell copies |
||
| 2167 | of the Software, and to permit persons to whom the Software is |
||
| 2168 | furnished to do so, subject to the following conditions: |
||
| 2169 | |||
| 2170 | The above copyright notice and this permission notice shall be |
||
| 2171 | included in all copies or substantial portions of the Software. |
||
| 2172 | |||
| 2173 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
||
| 2174 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
||
| 2175 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
||
| 2176 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR |
||
| 2177 | ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF |
||
| 2178 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
||
| 2179 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
||
| 2180 | |||
| 2181 | Except as contained in this notice, the names of the Centre for |
||
| 2182 | Digital Music; Queen Mary, University of London; and Chris Cannam |
||
| 2183 | shall not be used in advertising or otherwise to promote the sale, |
||
| 2184 | use or other dealings in this Software without prior written |
||
| 2185 | authorization. |
||
| 2186 | */ |
||
| 2187 | |||
| 2188 | #ifndef _TEST_MULTIPLE_RUNS_H_ |
||
| 2189 | #define _TEST_MULTIPLE_RUNS_H_ |
||
| 2190 | |||
| 2191 | #include "Test.h" |
||
| 2192 | #include "Tester.h" |
||
| 2193 | |||
| 2194 | class TestDistinctRuns : public Test |
||
| 2195 | {
|
||
| 2196 | public: |
||
| 2197 | TestDistinctRuns() : Test() { }
|
||
| 2198 | 8:3019cb6b538d | cannam | Results test(std::string key, Options options); |
| 2199 | 2:c9a4bd247497 | cannam | |
| 2200 | protected: |
||
| 2201 | static Tester::TestRegistrar<TestDistinctRuns> m_registrar; |
||
| 2202 | }; |
||
| 2203 | |||
| 2204 | class TestReset : public Test |
||
| 2205 | {
|
||
| 2206 | public: |
||
| 2207 | TestReset() : Test() { }
|
||
| 2208 | 8:3019cb6b538d | cannam | Results test(std::string key, Options options); |
| 2209 | 2:c9a4bd247497 | cannam | |
| 2210 | protected: |
||
| 2211 | static Tester::TestRegistrar<TestReset> m_registrar; |
||
| 2212 | }; |
||
| 2213 | |||
| 2214 | class TestInterleavedRuns : public Test |
||
| 2215 | {
|
||
| 2216 | public: |
||
| 2217 | TestInterleavedRuns() : Test() { }
|
||
| 2218 | 8:3019cb6b538d | cannam | Results test(std::string key, Options options); |
| 2219 | 2:c9a4bd247497 | cannam | |
| 2220 | protected: |
||
| 2221 | static Tester::TestRegistrar<TestInterleavedRuns> m_registrar; |
||
| 2222 | }; |
||
| 2223 | |||
| 2224 | 5:6a279da6fdd7 | cannam | class TestDifferentStartTimes : public Test |
| 2225 | {
|
||
| 2226 | public: |
||
| 2227 | TestDifferentStartTimes() : Test() { }
|
||
| 2228 | 8:3019cb6b538d | cannam | Results test(std::string key, Options options); |
| 2229 | 5:6a279da6fdd7 | cannam | |
| 2230 | protected: |
||
| 2231 | static Tester::TestRegistrar<TestDifferentStartTimes> m_registrar; |
||
| 2232 | }; |
||
| 2233 | |||
| 2234 | 2:c9a4bd247497 | cannam | #endif |
| 2235 | 3:0f65bb22172b | cannam | /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ |
| 2236 | |||
| 2237 | /* |
||
| 2238 | Vamp Plugin Tester |
||
| 2239 | Chris Cannam, cannam@all-day-breakfast.com |
||
| 2240 | Centre for Digital Music, Queen Mary, University of London. |
||
| 2241 | 42:f1e8e14e9c96 | Chris | Copyright 2009-2014 QMUL. |
| 2242 | 3:0f65bb22172b | cannam | |
| 2243 | This program loads a Vamp plugin and tests its susceptibility to a |
||
| 2244 | number of common pitfalls, including handling of extremes of input |
||
| 2245 | data. If you can think of any additional useful tests that are |
||
| 2246 | easily added, please send them to me. |
||
| 2247 | |||
| 2248 | Permission is hereby granted, free of charge, to any person |
||
| 2249 | obtaining a copy of this software and associated documentation |
||
| 2250 | files (the "Software"), to deal in the Software without |
||
| 2251 | restriction, including without limitation the rights to use, copy, |
||
| 2252 | modify, merge, publish, distribute, sublicense, and/or sell copies |
||
| 2253 | of the Software, and to permit persons to whom the Software is |
||
| 2254 | furnished to do so, subject to the following conditions: |
||
| 2255 | |||
| 2256 | The above copyright notice and this permission notice shall be |
||
| 2257 | included in all copies or substantial portions of the Software. |
||
| 2258 | |||
| 2259 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
||
| 2260 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
||
| 2261 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
||
| 2262 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR |
||
| 2263 | ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF |
||
| 2264 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
||
| 2265 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
||
| 2266 | |||
| 2267 | Except as contained in this notice, the names of the Centre for |
||
| 2268 | Digital Music; Queen Mary, University of London; and Chris Cannam |
||
| 2269 | shall not be used in advertising or otherwise to promote the sale, |
||
| 2270 | use or other dealings in this Software without prior written |
||
| 2271 | authorization. |
||
| 2272 | */ |
||
| 2273 | |||
| 2274 | #include "TestOutputs.h" |
||
| 2275 | |||
| 2276 | #include <vamp-hostsdk/Plugin.h> |
||
| 2277 | 4:d8724c5a6d83 | cannam | #include <vamp-hostsdk/PluginLoader.h> |
| 2278 | 3:0f65bb22172b | cannam | using namespace Vamp; |
| 2279 | 4:d8724c5a6d83 | cannam | using namespace Vamp::HostExt; |
| 2280 | 3:0f65bb22172b | cannam | |
| 2281 | #include <set> |
||
| 2282 | #include <memory> |
||
| 2283 | using namespace std; |
||
| 2284 | |||
| 2285 | #include <cmath> |
||
| 2286 | |||
| 2287 | Tester::TestRegistrar<TestOutputNumbers> |
||
| 2288 | 39:07144cdcbedf | Chris | TestOutputNumbers::m_registrar("B1", "Output number mismatching");
|
| 2289 | 3:0f65bb22172b | cannam | |
| 2290 | Tester::TestRegistrar<TestTimestamps> |
||
| 2291 | 39:07144cdcbedf | Chris | TestTimestamps::m_registrar("B2", "Invalid or dubious timestamp usage");
|
| 2292 | 3:0f65bb22172b | cannam | |
| 2293 | static const size_t _step = 1000; |
||
| 2294 | |||
| 2295 | Test::Results |
||
| 2296 | 8:3019cb6b538d | cannam | TestOutputNumbers::test(string key, Options options) |
| 2297 | 3:0f65bb22172b | cannam | {
|
| 2298 | int rate = 44100; |
||
| 2299 | auto_ptr<Plugin> p(load(key, rate)); |
||
| 2300 | Plugin::FeatureSet f; |
||
| 2301 | Results r; |
||
| 2302 | float **data = 0; |
||
| 2303 | size_t channels = 0; |
||
| 2304 | size_t count = 100; |
||
| 2305 | |||
| 2306 | if (!initAdapted(p.get(), channels, _step, _step, r)) return r; |
||
| 2307 | if (!data) data = createTestAudio(channels, _step, count); |
||
| 2308 | for (size_t i = 0; i < count; ++i) {
|
||
| 2309 | 67:fa66ee7dcf08 | Chris | float **ptr = new float *[channels]; |
| 2310 | 3:0f65bb22172b | cannam | size_t idx = i * _step; |
| 2311 | for (size_t c = 0; c < channels; ++c) ptr[c] = data[c] + idx; |
||
| 2312 | RealTime timestamp = RealTime::frame2RealTime(idx, rate); |
||
| 2313 | Plugin::FeatureSet fs = p->process(ptr, timestamp); |
||
| 2314 | 67:fa66ee7dcf08 | Chris | delete[] ptr; |
| 2315 | 3:0f65bb22172b | cannam | appendFeatures(f, fs); |
| 2316 | } |
||
| 2317 | Plugin::FeatureSet fs = p->getRemainingFeatures(); |
||
| 2318 | appendFeatures(f, fs); |
||
| 2319 | if (data) destroyTestAudio(data, channels); |
||
| 2320 | |||
| 2321 | std::set<int> used; |
||
| 2322 | Plugin::OutputList outputs = p->getOutputDescriptors(); |
||
| 2323 | 5:6a279da6fdd7 | cannam | for (Plugin::FeatureSet::const_iterator i = f.begin(); |
| 2324 | i != f.end(); ++i) {
|
||
| 2325 | 3:0f65bb22172b | cannam | int o = i->first; |
| 2326 | used.insert(o); |
||
| 2327 | 4:d8724c5a6d83 | cannam | if (o < 0 || o >= (int)outputs.size()) {
|
| 2328 | 3:0f65bb22172b | cannam | r.push_back(error("Data returned on nonexistent output"));
|
| 2329 | } |
||
| 2330 | } |
||
| 2331 | 4:d8724c5a6d83 | cannam | for (int o = 0; o < (int)outputs.size(); ++o) {
|
| 2332 | 3:0f65bb22172b | cannam | if (used.find(o) == used.end()) {
|
| 2333 | 12:7dd6a549b2f9 | cannam | r.push_back(note("No results returned for output \"" + outputs[o].identifier + "\""));
|
| 2334 | 4:d8724c5a6d83 | cannam | } |
| 2335 | 3:0f65bb22172b | cannam | } |
| 2336 | |||
| 2337 | 8:3019cb6b538d | cannam | if (!r.empty() && (options & Verbose)) dump(f); |
| 2338 | 3:0f65bb22172b | cannam | return r; |
| 2339 | } |
||
| 2340 | |||
| 2341 | Test::Results |
||
| 2342 | 8:3019cb6b538d | cannam | TestTimestamps::test(string key, Options options) |
| 2343 | 3:0f65bb22172b | cannam | {
|
| 2344 | int rate = 44100; |
||
| 2345 | 4:d8724c5a6d83 | cannam | |
| 2346 | // we want to be sure that a buffer size adapter is not used: |
||
| 2347 | auto_ptr<Plugin> p(PluginLoader::getInstance()->loadPlugin |
||
| 2348 | (key, rate, PluginLoader::ADAPT_ALL_SAFE)); |
||
| 2349 | |||
| 2350 | 60:9f2a6d843639 | Chris | Results r; |
| 2351 | 3:0f65bb22172b | cannam | Plugin::FeatureSet f; |
| 2352 | float **data = 0; |
||
| 2353 | size_t channels = 0; |
||
| 2354 | size_t step = 0, block = 0; |
||
| 2355 | size_t count = 100; |
||
| 2356 | |||
| 2357 | if (!initDefaults(p.get(), channels, step, block, r)) return r; |
||
| 2358 | 60:9f2a6d843639 | Chris | |
| 2359 | Plugin::OutputList outputs = p->getOutputDescriptors(); |
||
| 2360 | for (int i = 0; i < (int)outputs.size(); ++i) {
|
||
| 2361 | if (outputs[i].sampleType == Plugin::OutputDescriptor::FixedSampleRate && |
||
| 2362 | outputs[i].sampleRate == 0.f) {
|
||
| 2363 | r.push_back(error("Plugin output \"" + outputs[i].identifier +
|
||
| 2364 | "\" has FixedSampleRate but gives sample rate as 0")); |
||
| 2365 | } |
||
| 2366 | } |
||
| 2367 | |||
| 2368 | 3:0f65bb22172b | cannam | if (!data) data = createTestAudio(channels, block, count); |
| 2369 | for (size_t i = 0; i < count; ++i) {
|
||
| 2370 | 67:fa66ee7dcf08 | Chris | float **ptr = new float *[channels]; |
| 2371 | 3:0f65bb22172b | cannam | size_t idx = i * step; |
| 2372 | for (size_t c = 0; c < channels; ++c) ptr[c] = data[c] + idx; |
||
| 2373 | RealTime timestamp = RealTime::frame2RealTime(idx, rate); |
||
| 2374 | Plugin::FeatureSet fs = p->process(ptr, timestamp); |
||
| 2375 | 67:fa66ee7dcf08 | Chris | delete[] ptr; |
| 2376 | 3:0f65bb22172b | cannam | appendFeatures(f, fs); |
| 2377 | } |
||
| 2378 | Plugin::FeatureSet fs = p->getRemainingFeatures(); |
||
| 2379 | appendFeatures(f, fs); |
||
| 2380 | if (data) destroyTestAudio(data, channels); |
||
| 2381 | |||
| 2382 | 5:6a279da6fdd7 | cannam | for (Plugin::FeatureSet::const_iterator i = f.begin(); |
| 2383 | i != f.end(); ++i) {
|
||
| 2384 | 3:0f65bb22172b | cannam | const Plugin::OutputDescriptor &o = outputs[i->first]; |
| 2385 | const Plugin::FeatureList &fl = i->second; |
||
| 2386 | for (int j = 0; j < (int)fl.size(); ++j) {
|
||
| 2387 | 5:6a279da6fdd7 | cannam | const Plugin::Feature &fe = fl[j]; |
| 2388 | 3:0f65bb22172b | cannam | switch (o.sampleType) {
|
| 2389 | case Plugin::OutputDescriptor::OneSamplePerStep: |
||
| 2390 | 5:6a279da6fdd7 | cannam | if (fe.hasTimestamp) {
|
| 2391 | 31:ed9d3c27f687 | Chris | r.push_back(note("Plugin returns features with timestamps on OneSamplePerStep output \"" + o.identifier + "\""));
|
| 2392 | 3:0f65bb22172b | cannam | } |
| 2393 | 5:6a279da6fdd7 | cannam | if (fe.hasDuration) {
|
| 2394 | 31:ed9d3c27f687 | Chris | r.push_back(note("Plugin returns features with durations on OneSamplePerStep output \"" + o.identifier + "\""));
|
| 2395 | 3:0f65bb22172b | cannam | } |
| 2396 | break; |
||
| 2397 | case Plugin::OutputDescriptor::FixedSampleRate: |
||
| 2398 | break; |
||
| 2399 | case Plugin::OutputDescriptor::VariableSampleRate: |
||
| 2400 | 5:6a279da6fdd7 | cannam | if (!fe.hasTimestamp) {
|
| 2401 | 31:ed9d3c27f687 | Chris | r.push_back(error("Plugin returns features with no timestamps on VariableSampleRate output \"" + o.identifier + "\""));
|
| 2402 | 3:0f65bb22172b | cannam | } |
| 2403 | break; |
||
| 2404 | } |
||
| 2405 | } |
||
| 2406 | } |
||
| 2407 | |||
| 2408 | 8:3019cb6b538d | cannam | if (!r.empty() && (options & Verbose)) dump(f); |
| 2409 | 3:0f65bb22172b | cannam | return r; |
| 2410 | } |
||
| 2411 | /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ |
||
| 2412 | |||
| 2413 | /* |
||
| 2414 | Vamp Plugin Tester |
||
| 2415 | Chris Cannam, cannam@all-day-breakfast.com |
||
| 2416 | Centre for Digital Music, Queen Mary, University of London. |
||
| 2417 | 42:f1e8e14e9c96 | Chris | Copyright 2009-2014 QMUL. |
| 2418 | 3:0f65bb22172b | cannam | |
| 2419 | This program loads a Vamp plugin and tests its susceptibility to a |
||
| 2420 | number of common pitfalls, including handling of extremes of input |
||
| 2421 | data. If you can think of any additional useful tests that are |
||
| 2422 | easily added, please send them to me. |
||
| 2423 | |||
| 2424 | Permission is hereby granted, free of charge, to any person |
||
| 2425 | obtaining a copy of this software and associated documentation |
||
| 2426 | files (the "Software"), to deal in the Software without |
||
| 2427 | restriction, including without limitation the rights to use, copy, |
||
| 2428 | modify, merge, publish, distribute, sublicense, and/or sell copies |
||
| 2429 | of the Software, and to permit persons to whom the Software is |
||
| 2430 | furnished to do so, subject to the following conditions: |
||
| 2431 | |||
| 2432 | The above copyright notice and this permission notice shall be |
||
| 2433 | included in all copies or substantial portions of the Software. |
||
| 2434 | |||
| 2435 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
||
| 2436 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
||
| 2437 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
||
| 2438 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR |
||
| 2439 | ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF |
||
| 2440 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
||
| 2441 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
||
| 2442 | |||
| 2443 | Except as contained in this notice, the names of the Centre for |
||
| 2444 | Digital Music; Queen Mary, University of London; and Chris Cannam |
||
| 2445 | shall not be used in advertising or otherwise to promote the sale, |
||
| 2446 | use or other dealings in this Software without prior written |
||
| 2447 | authorization. |
||
| 2448 | */ |
||
| 2449 | |||
| 2450 | #ifndef _TEST_OUTPUTS_H_ |
||
| 2451 | #define _TEST_OUTPUTS_H_ |
||
| 2452 | |||
| 2453 | #include "Test.h" |
||
| 2454 | #include "Tester.h" |
||
| 2455 | |||
| 2456 | class TestOutputNumbers : public Test |
||
| 2457 | {
|
||
| 2458 | public: |
||
| 2459 | TestOutputNumbers() : Test() { }
|
||
| 2460 | 8:3019cb6b538d | cannam | Results test(std::string key, Options options); |
| 2461 | 3:0f65bb22172b | cannam | |
| 2462 | protected: |
||
| 2463 | static Tester::TestRegistrar<TestOutputNumbers> m_registrar; |
||
| 2464 | }; |
||
| 2465 | |||
| 2466 | class TestTimestamps : public Test |
||
| 2467 | {
|
||
| 2468 | public: |
||
| 2469 | TestTimestamps() : Test() { }
|
||
| 2470 | 8:3019cb6b538d | cannam | Results test(std::string key, Options options); |
| 2471 | 3:0f65bb22172b | cannam | |
| 2472 | protected: |
||
| 2473 | static Tester::TestRegistrar<TestTimestamps> m_registrar; |
||
| 2474 | }; |
||
| 2475 | |||
| 2476 | |||
| 2477 | #endif |
||
| 2478 | 0:f89128a316e7 | cannam | /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ |
| 2479 | |||
| 2480 | /* |
||
| 2481 | Vamp Plugin Tester |
||
| 2482 | Chris Cannam, cannam@all-day-breakfast.com |
||
| 2483 | Centre for Digital Music, Queen Mary, University of London. |
||
| 2484 | 42:f1e8e14e9c96 | Chris | Copyright 2009-2014 QMUL. |
| 2485 | 0:f89128a316e7 | cannam | |
| 2486 | This program loads a Vamp plugin and tests its susceptibility to a |
||
| 2487 | number of common pitfalls, including handling of extremes of input |
||
| 2488 | data. If you can think of any additional useful tests that are |
||
| 2489 | easily added, please send them to me. |
||
| 2490 | |||
| 2491 | Permission is hereby granted, free of charge, to any person |
||
| 2492 | obtaining a copy of this software and associated documentation |
||
| 2493 | files (the "Software"), to deal in the Software without |
||
| 2494 | restriction, including without limitation the rights to use, copy, |
||
| 2495 | modify, merge, publish, distribute, sublicense, and/or sell copies |
||
| 2496 | of the Software, and to permit persons to whom the Software is |
||
| 2497 | furnished to do so, subject to the following conditions: |
||
| 2498 | |||
| 2499 | The above copyright notice and this permission notice shall be |
||
| 2500 | included in all copies or substantial portions of the Software. |
||
| 2501 | |||
| 2502 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
||
| 2503 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
||
| 2504 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
||
| 2505 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR |
||
| 2506 | ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF |
||
| 2507 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
||
| 2508 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
||
| 2509 | |||
| 2510 | Except as contained in this notice, the names of the Centre for |
||
| 2511 | Digital Music; Queen Mary, University of London; and Chris Cannam |
||
| 2512 | shall not be used in advertising or otherwise to promote the sale, |
||
| 2513 | use or other dealings in this Software without prior written |
||
| 2514 | authorization. |
||
| 2515 | */ |
||
| 2516 | |||
| 2517 | #include "TestStaticData.h" |
||
| 2518 | |||
| 2519 | #include <vamp-hostsdk/Plugin.h> |
||
| 2520 | 35:b700f37dc118 | Chris | #include <vamp-hostsdk/PluginLoader.h> |
| 2521 | 0:f89128a316e7 | cannam | using namespace Vamp; |
| 2522 | 35:b700f37dc118 | Chris | using namespace Vamp::HostExt; |
| 2523 | 0:f89128a316e7 | cannam | |
| 2524 | #include <memory> |
||
| 2525 | using namespace std; |
||
| 2526 | |||
| 2527 | #include <cmath> |
||
| 2528 | |||
| 2529 | Tester::TestRegistrar<TestIdentifiers> |
||
| 2530 | 39:07144cdcbedf | Chris | TestIdentifiers::m_registrar("A1", "Invalid identifiers");
|
| 2531 | 0:f89128a316e7 | cannam | |
| 2532 | Tester::TestRegistrar<TestEmptyFields> |
||
| 2533 | 39:07144cdcbedf | Chris | TestEmptyFields::m_registrar("A2", "Empty metadata fields");
|
| 2534 | 0:f89128a316e7 | cannam | |
| 2535 | Tester::TestRegistrar<TestValueRanges> |
||
| 2536 | 39:07144cdcbedf | Chris | TestValueRanges::m_registrar("A3", "Inappropriate value extents");
|
| 2537 | 0:f89128a316e7 | cannam | |
| 2538 | 35:b700f37dc118 | Chris | Tester::TestRegistrar<TestCategory> |
| 2539 | 79:69e7d4e1e68c | Chris | TestCategory::m_registrar("A4", "Missing category");
|
| 2540 | 35:b700f37dc118 | Chris | |
| 2541 | 0:f89128a316e7 | cannam | Test::Results |
| 2542 | 14:e48fdc8de790 | cannam | TestIdentifiers::test(string key, Options) |
| 2543 | 0:f89128a316e7 | cannam | {
|
| 2544 | auto_ptr<Plugin> p(load(key)); |
||
| 2545 | |||
| 2546 | Results r; |
||
| 2547 | 17:ea8865f488a0 | cannam | r.push_back(testIdentifier(p->getIdentifier(), "Plugin identifier")); |
| 2548 | 0:f89128a316e7 | cannam | |
| 2549 | Plugin::ParameterList params = p->getParameterDescriptors(); |
||
| 2550 | for (int i = 0; i < (int)params.size(); ++i) {
|
||
| 2551 | 17:ea8865f488a0 | cannam | r.push_back(testIdentifier(params[i].identifier, "Parameter identifier")); |
| 2552 | 0:f89128a316e7 | cannam | } |
| 2553 | |||
| 2554 | Plugin::OutputList outputs = p->getOutputDescriptors(); |
||
| 2555 | for (int i = 0; i < (int)outputs.size(); ++i) {
|
||
| 2556 | 17:ea8865f488a0 | cannam | r.push_back(testIdentifier(outputs[i].identifier, "Output identifier")); |
| 2557 | 0:f89128a316e7 | cannam | } |
| 2558 | |||
| 2559 | return r; |
||
| 2560 | } |
||
| 2561 | |||
| 2562 | Test::Result |
||
| 2563 | TestIdentifiers::testIdentifier(string identifier, string desc) |
||
| 2564 | {
|
||
| 2565 | for (int i = 0; i < (int)identifier.length(); ++i) {
|
||
| 2566 | char c = identifier[i]; |
||
| 2567 | if (c >= 'a' && c <= 'z') continue; |
||
| 2568 | if (c >= 'A' && c <= 'Z') continue; |
||
| 2569 | if (c >= '0' && c <= '9') continue; |
||
| 2570 | if (c == '_' || c == '-') continue; |
||
| 2571 | return error |
||
| 2572 | (desc + " \"" + identifier + |
||
| 2573 | "\" contains invalid character(s); permitted are: [a-zA-Z0-9_-]"); |
||
| 2574 | } |
||
| 2575 | return success(); |
||
| 2576 | } |
||
| 2577 | |||
| 2578 | Test::Results |
||
| 2579 | 14:e48fdc8de790 | cannam | TestEmptyFields::test(string key, Options) |
| 2580 | 0:f89128a316e7 | cannam | {
|
| 2581 | auto_ptr<Plugin> p(load(key)); |
||
| 2582 | |||
| 2583 | Results r; |
||
| 2584 | |||
| 2585 | 17:ea8865f488a0 | cannam | r.push_back(testMandatory(p->getName(), "Plugin name")); |
| 2586 | r.push_back(testRecommended(p->getDescription(), "Plugin description")); |
||
| 2587 | r.push_back(testRecommended(p->getMaker(), "Plugin maker")); |
||
| 2588 | r.push_back(testRecommended(p->getCopyright(), "Plugin copyright")); |
||
| 2589 | 0:f89128a316e7 | cannam | |
| 2590 | Plugin::ParameterList params = p->getParameterDescriptors(); |
||
| 2591 | for (int i = 0; i < (int)params.size(); ++i) {
|
||
| 2592 | r.push_back(testMandatory |
||
| 2593 | (params[i].name, |
||
| 2594 | 17:ea8865f488a0 | cannam | "Plugin parameter \"" + params[i].identifier + "\" name")); |
| 2595 | 0:f89128a316e7 | cannam | r.push_back(testRecommended |
| 2596 | (params[i].description, |
||
| 2597 | 17:ea8865f488a0 | cannam | "Plugin parameter \"" + params[i].identifier + "\" description")); |
| 2598 | 0:f89128a316e7 | cannam | } |
| 2599 | |||
| 2600 | Plugin::OutputList outputs = p->getOutputDescriptors(); |
||
| 2601 | for (int i = 0; i < (int)outputs.size(); ++i) {
|
||
| 2602 | r.push_back(testMandatory |
||
| 2603 | (outputs[i].name, |
||
| 2604 | 17:ea8865f488a0 | cannam | "Plugin output \"" + outputs[i].identifier + "\" name")); |
| 2605 | 0:f89128a316e7 | cannam | r.push_back(testRecommended |
| 2606 | (outputs[i].description, |
||
| 2607 | 17:ea8865f488a0 | cannam | "Plugin output \"" + outputs[i].identifier + "\" description")); |
| 2608 | 0:f89128a316e7 | cannam | } |
| 2609 | |||
| 2610 | return r; |
||
| 2611 | } |
||
| 2612 | |||
| 2613 | Test::Result |
||
| 2614 | TestEmptyFields::testMandatory(string text, string desc) |
||
| 2615 | {
|
||
| 2616 | if (text == "") {
|
||
| 2617 | return error(desc + " is empty"); |
||
| 2618 | } |
||
| 2619 | return success(); |
||
| 2620 | } |
||
| 2621 | |||
| 2622 | Test::Result |
||
| 2623 | TestEmptyFields::testRecommended(string text, string desc) |
||
| 2624 | {
|
||
| 2625 | if (text == "") {
|
||
| 2626 | return warning(desc + " is empty"); |
||
| 2627 | 54:314eea778b80 | Chris | } else if (text == "Licence information not available." || |
| 2628 | text == "VamPy Plugin." || |
||
| 2629 | text == "Not given. (Hint: Implement getDescription method.)" || |
||
| 2630 | text == "VamPy Plugin (Noname)") {
|
||
| 2631 | return warning(desc + " is missing (returns VamPy boilerplate text)"); |
||
| 2632 | 0:f89128a316e7 | cannam | } |
| 2633 | return success(); |
||
| 2634 | } |
||
| 2635 | |||
| 2636 | Test::Results |
||
| 2637 | 14:e48fdc8de790 | cannam | TestValueRanges::test(string key, Options) |
| 2638 | 0:f89128a316e7 | cannam | {
|
| 2639 | auto_ptr<Plugin> p(load(key)); |
||
| 2640 | |||
| 2641 | Results r; |
||
| 2642 | |||
| 2643 | Plugin::ParameterList params = p->getParameterDescriptors(); |
||
| 2644 | for (int i = 0; i < (int)params.size(); ++i) {
|
||
| 2645 | Plugin::ParameterDescriptor &pd(params[i]); |
||
| 2646 | 17:ea8865f488a0 | cannam | string pfx("Plugin parameter \"" + pd.identifier + "\"");
|
| 2647 | 0:f89128a316e7 | cannam | float min = pd.minValue; |
| 2648 | float max = pd.maxValue; |
||
| 2649 | float deft = pd.defaultValue; |
||
| 2650 | if (max <= min) {
|
||
| 2651 | r.push_back(error(pfx + " maxValue <= minValue")); |
||
| 2652 | } |
||
| 2653 | if (deft < min || deft > max) {
|
||
| 2654 | r.push_back(error(pfx + " defaultValue out of range")); |
||
| 2655 | } |
||
| 2656 | if (pd.isQuantized) {
|
||
| 2657 | if (pd.quantizeStep == 0.f) {
|
||
| 2658 | r.push_back(error(pfx + " is quantized, but quantize step is zero")); |
||
| 2659 | } else {
|
||
| 2660 | |||
| 2661 | float epsilon = 0.00001f; |
||
| 2662 | int qty = int((max - min) / pd.quantizeStep + 0.5); |
||
| 2663 | float target = min + pd.quantizeStep * qty; |
||
| 2664 | if (fabsf(max - target) > epsilon) {
|
||
| 2665 | r.push_back(warning(pfx + " value range is not a multiple of quantize step")); |
||
| 2666 | } |
||
| 2667 | |||
| 2668 | if (!pd.valueNames.empty()) {
|
||
| 2669 | if ((int)pd.valueNames.size() < qty+1) {
|
||
| 2670 | r.push_back(warning(pfx + " has fewer value names than quantize steps")); |
||
| 2671 | } else if ((int)pd.valueNames.size() > qty+1) {
|
||
| 2672 | r.push_back(warning(pfx + " has more value names than quantize steps")); |
||
| 2673 | } |
||
| 2674 | } |
||
| 2675 | |||
| 2676 | qty = int((deft - min) / pd.quantizeStep + 0.5); |
||
| 2677 | target = min + pd.quantizeStep * qty; |
||
| 2678 | if (fabsf(deft - target) > epsilon) {
|
||
| 2679 | r.push_back(warning(pfx + " default value is not a multiple of quantize step beyond minimum")); |
||
| 2680 | } |
||
| 2681 | } |
||
| 2682 | } |
||
| 2683 | } |
||
| 2684 | |||
| 2685 | return r; |
||
| 2686 | } |
||
| 2687 | |||
| 2688 | 35:b700f37dc118 | Chris | Test::Results |
| 2689 | TestCategory::test(string key, Options) |
||
| 2690 | {
|
||
| 2691 | PluginLoader::PluginCategoryHierarchy hierarchy = |
||
| 2692 | PluginLoader::getInstance()->getPluginCategory(key); |
||
| 2693 | |||
| 2694 | Results r; |
||
| 2695 | 0:f89128a316e7 | cannam | |
| 2696 | 35:b700f37dc118 | Chris | if (hierarchy.empty()) {
|
| 2697 | r.push_back(warning("Plugin category missing or cannot be loaded (no .cat file?)"));
|
||
| 2698 | } |
||
| 2699 | |||
| 2700 | return r; |
||
| 2701 | } |
||
| 2702 | |||
| 2703 | 0:f89128a316e7 | cannam | /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ |
| 2704 | |||
| 2705 | /* |
||
| 2706 | Vamp Plugin Tester |
||
| 2707 | Chris Cannam, cannam@all-day-breakfast.com |
||
| 2708 | Centre for Digital Music, Queen Mary, University of London. |
||
| 2709 | 42:f1e8e14e9c96 | Chris | Copyright 2009-2014 QMUL. |
| 2710 | 0:f89128a316e7 | cannam | |
| 2711 | This program loads a Vamp plugin and tests its susceptibility to a |
||
| 2712 | number of common pitfalls, including handling of extremes of input |
||
| 2713 | data. If you can think of any additional useful tests that are |
||
| 2714 | easily added, please send them to me. |
||
| 2715 | |||
| 2716 | Permission is hereby granted, free of charge, to any person |
||
| 2717 | obtaining a copy of this software and associated documentation |
||
| 2718 | files (the "Software"), to deal in the Software without |
||
| 2719 | restriction, including without limitation the rights to use, copy, |
||
| 2720 | modify, merge, publish, distribute, sublicense, and/or sell copies |
||
| 2721 | of the Software, and to permit persons to whom the Software is |
||
| 2722 | furnished to do so, subject to the following conditions: |
||
| 2723 | |||
| 2724 | The above copyright notice and this permission notice shall be |
||
| 2725 | included in all copies or substantial portions of the Software. |
||
| 2726 | |||
| 2727 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
||
| 2728 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
||
| 2729 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
||
| 2730 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR |
||
| 2731 | ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF |
||
| 2732 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
||
| 2733 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
||
| 2734 | |||
| 2735 | Except as contained in this notice, the names of the Centre for |
||
| 2736 | Digital Music; Queen Mary, University of London; and Chris Cannam |
||
| 2737 | shall not be used in advertising or otherwise to promote the sale, |
||
| 2738 | use or other dealings in this Software without prior written |
||
| 2739 | authorization. |
||
| 2740 | */ |
||
| 2741 | |||
| 2742 | #ifndef _TEST_STATIC_DATA_H_ |
||
| 2743 | #define _TEST_STATIC_DATA_H_ |
||
| 2744 | |||
| 2745 | #include "Test.h" |
||
| 2746 | #include "Tester.h" |
||
| 2747 | |||
| 2748 | class TestIdentifiers : public Test |
||
| 2749 | {
|
||
| 2750 | public: |
||
| 2751 | TestIdentifiers() : Test() { }
|
||
| 2752 | 8:3019cb6b538d | cannam | Results test(std::string key, Options options); |
| 2753 | 0:f89128a316e7 | cannam | |
| 2754 | protected: |
||
| 2755 | Result testIdentifier(std::string ident, std::string desc); |
||
| 2756 | static Tester::TestRegistrar<TestIdentifiers> m_registrar; |
||
| 2757 | }; |
||
| 2758 | |||
| 2759 | class TestEmptyFields : public Test |
||
| 2760 | {
|
||
| 2761 | public: |
||
| 2762 | TestEmptyFields() : Test() { }
|
||
| 2763 | 8:3019cb6b538d | cannam | Results test(std::string key, Options options); |
| 2764 | 0:f89128a316e7 | cannam | |
| 2765 | protected: |
||
| 2766 | Result testMandatory(std::string text, std::string desc); |
||
| 2767 | Result testRecommended(std::string text, std::string desc); |
||
| 2768 | static Tester::TestRegistrar<TestEmptyFields> m_registrar; |
||
| 2769 | }; |
||
| 2770 | |||
| 2771 | class TestValueRanges : public Test |
||
| 2772 | {
|
||
| 2773 | public: |
||
| 2774 | TestValueRanges() : Test() { }
|
||
| 2775 | 8:3019cb6b538d | cannam | Results test(std::string key, Options options); |
| 2776 | 0:f89128a316e7 | cannam | |
| 2777 | protected: |
||
| 2778 | static Tester::TestRegistrar<TestValueRanges> m_registrar; |
||
| 2779 | }; |
||
| 2780 | |||
| 2781 | 35:b700f37dc118 | Chris | class TestCategory : public Test |
| 2782 | {
|
||
| 2783 | public: |
||
| 2784 | TestCategory() : Test() { }
|
||
| 2785 | Results test(std::string key, Options options); |
||
| 2786 | |||
| 2787 | protected: |
||
| 2788 | static Tester::TestRegistrar<TestCategory> m_registrar; |
||
| 2789 | }; |
||
| 2790 | |||
| 2791 | 0:f89128a316e7 | cannam | #endif |
| 2792 | /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ |
||
| 2793 | |||
| 2794 | /* |
||
| 2795 | Vamp Plugin Tester |
||
| 2796 | Chris Cannam, cannam@all-day-breakfast.com |
||
| 2797 | Centre for Digital Music, Queen Mary, University of London. |
||
| 2798 | 42:f1e8e14e9c96 | Chris | Copyright 2009-2014 QMUL. |
| 2799 | 0:f89128a316e7 | cannam | |
| 2800 | This program loads a Vamp plugin and tests its susceptibility to a |
||
| 2801 | number of common pitfalls, including handling of extremes of input |
||
| 2802 | data. If you can think of any additional useful tests that are |
||
| 2803 | easily added, please send them to me. |
||
| 2804 | |||
| 2805 | Permission is hereby granted, free of charge, to any person |
||
| 2806 | obtaining a copy of this software and associated documentation |
||
| 2807 | files (the "Software"), to deal in the Software without |
||
| 2808 | restriction, including without limitation the rights to use, copy, |
||
| 2809 | modify, merge, publish, distribute, sublicense, and/or sell copies |
||
| 2810 | of the Software, and to permit persons to whom the Software is |
||
| 2811 | furnished to do so, subject to the following conditions: |
||
| 2812 | |||
| 2813 | The above copyright notice and this permission notice shall be |
||
| 2814 | included in all copies or substantial portions of the Software. |
||
| 2815 | |||
| 2816 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
||
| 2817 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
||
| 2818 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
||
| 2819 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR |
||
| 2820 | ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF |
||
| 2821 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
||
| 2822 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
||
| 2823 | |||
| 2824 | Except as contained in this notice, the names of the Centre for |
||
| 2825 | Digital Music; Queen Mary, University of London; and Chris Cannam |
||
| 2826 | shall not be used in advertising or otherwise to promote the sale, |
||
| 2827 | use or other dealings in this Software without prior written |
||
| 2828 | authorization. |
||
| 2829 | */ |
||
| 2830 | |||
| 2831 | #include <vamp-hostsdk/PluginHostAdapter.h> |
||
| 2832 | #include <vamp-hostsdk/PluginInputDomainAdapter.h> |
||
| 2833 | #include <vamp-hostsdk/PluginLoader.h> |
||
| 2834 | |||
| 2835 | #include <iostream> |
||
| 2836 | |||
| 2837 | #include <cstring> |
||
| 2838 | #include <cstdlib> |
||
| 2839 | #include <cmath> |
||
| 2840 | 4:d8724c5a6d83 | cannam | #include <set> |
| 2841 | 0:f89128a316e7 | cannam | |
| 2842 | #include "Tester.h" |
||
| 2843 | |||
| 2844 | using Vamp::Plugin; |
||
| 2845 | using Vamp::PluginHostAdapter; |
||
| 2846 | using Vamp::RealTime; |
||
| 2847 | using Vamp::HostExt::PluginLoader; |
||
| 2848 | using Vamp::HostExt::PluginWrapper; |
||
| 2849 | using Vamp::HostExt::PluginInputDomainAdapter; |
||
| 2850 | |||
| 2851 | using namespace std; |
||
| 2852 | |||
| 2853 | 39:07144cdcbedf | Chris | Tester::Tester(std::string key, Test::Options options, std::string singleTestId) : |
| 2854 | 8:3019cb6b538d | cannam | m_key(key), |
| 2855 | 39:07144cdcbedf | Chris | m_options(options), |
| 2856 | m_singleTest(singleTestId) |
||
| 2857 | 0:f89128a316e7 | cannam | {
|
| 2858 | } |
||
| 2859 | |||
| 2860 | Tester::~Tester() |
||
| 2861 | {
|
||
| 2862 | } |
||
| 2863 | |||
| 2864 | 39:07144cdcbedf | Chris | Tester::NameIndex & |
| 2865 | Tester::nameIndex() |
||
| 2866 | {
|
||
| 2867 | static NameIndex ix; |
||
| 2868 | return ix; |
||
| 2869 | } |
||
| 2870 | |||
| 2871 | 0:f89128a316e7 | cannam | Tester::Registry & |
| 2872 | Tester::registry() |
||
| 2873 | {
|
||
| 2874 | static Registry r; |
||
| 2875 | return r; |
||
| 2876 | } |
||
| 2877 | |||
| 2878 | 40:649f32c7eb41 | Chris | void |
| 2879 | Tester::listTests() |
||
| 2880 | {
|
||
| 2881 | cout << endl; |
||
| 2882 | cout << "Total tests: " << nameIndex().size() << "\n" << endl; |
||
| 2883 | cout << "ID | Name" << endl; |
||
| 2884 | cout << "---+-----" << endl; |
||
| 2885 | for (NameIndex::const_iterator i = nameIndex().begin(); |
||
| 2886 | i != nameIndex().end(); ++i) {
|
||
| 2887 | cout << i->first << " | " << i->second << endl; |
||
| 2888 | } |
||
| 2889 | cout << endl; |
||
| 2890 | } |
||
| 2891 | |||
| 2892 | 0:f89128a316e7 | cannam | bool |
| 2893 | 4:d8724c5a6d83 | cannam | Tester::test(int ¬es, int &warnings, int &errors) |
| 2894 | 0:f89128a316e7 | cannam | {
|
| 2895 | /* |
||
| 2896 | |||
| 2897 | Things I would like to see tested: |
||
| 2898 | |||
| 2899 | * Identifiers for parameters, outputs, or plugin itself contain |
||
| 2900 | illegal characters - DONE |
||
| 2901 | |||
| 2902 | * Any of the plugin's name, maker etc fields are empty - DONE |
||
| 2903 | |||
| 2904 | 1:d7ef749300ed | cannam | * Default value of a parameter is not quantized as specified - DONE |
| 2905 | |||
| 2906 | * Parameter minValue >= maxValue, or defaultValue < minValue |
||
| 2907 | or > maxValue - DONE |
||
| 2908 | |||
| 2909 | * Plugin fails when given zero-length or very short input - DONE |
||
| 2910 | |||
| 2911 | * Plugin fails when given "all digital zeros" input - DONE |
||
| 2912 | |||
| 2913 | * Plugin fails when given input that exceeds +/-1 - DONE |
||
| 2914 | |||
| 2915 | * Plugin fails when given "normal" random input (just in case!) - DONE |
||
| 2916 | |||
| 2917 | 0:f89128a316e7 | cannam | * Plugin returns different results if another instance is |
| 2918 | 3:0f65bb22172b | cannam | constructed and run "interleaved" with it (from same thread) - DONE |
| 2919 | 0:f89128a316e7 | cannam | |
| 2920 | * Plugin's returned timestamps do not change as expected when |
||
| 2921 | run with a different base timestamp for input (though there |
||
| 2922 | 5:6a279da6fdd7 | cannam | could be legitimate reasons for this) - DONE |
| 2923 | 0:f89128a316e7 | cannam | |
| 2924 | * Plugin produces different results on second run, after reset |
||
| 2925 | 3:0f65bb22172b | cannam | called - DONE |
| 2926 | 0:f89128a316e7 | cannam | |
| 2927 | * Initial value of a parameter on plugin construction differs |
||
| 2928 | from its default value (i.e. plugin produces different |
||
| 2929 | results depending on whether parameter is set explicitly by |
||
| 2930 | 6:ba3c8cc649d3 | cannam | host to default value or not) - DONE |
| 2931 | 0:f89128a316e7 | cannam | |
| 2932 | * If a plugin reports any programs, selecting default program |
||
| 2933 | 6:ba3c8cc649d3 | cannam | explicitly changes results (as for default parameters) - DONE |
| 2934 | 0:f89128a316e7 | cannam | |
| 2935 | * Output feature does not hasTimestamp when output type is |
||
| 2936 | 5:6a279da6fdd7 | cannam | VariableSampleRate - DONE |
| 2937 | 0:f89128a316e7 | cannam | |
| 2938 | * Output feature hasTimestamp or hasDuration when output type is |
||
| 2939 | 5:6a279da6fdd7 | cannam | OneSamplePerStep (warning only, this is not an error) - DONE |
| 2940 | 0:f89128a316e7 | cannam | |
| 2941 | * Plugin returns features whose output numbers do not have |
||
| 2942 | 5:6a279da6fdd7 | cannam | a corresponding record in output descriptor list - DONE |
| 2943 | 0:f89128a316e7 | cannam | |
| 2944 | * Plugin fails to return any features on some output (warning |
||
| 2945 | 5:6a279da6fdd7 | cannam | only) - DONE |
| 2946 | 0:f89128a316e7 | cannam | |
| 2947 | * Constructor takes a long time to run. A fuzzy concept, but |
||
| 2948 | suggests that some work should have been deferred to |
||
| 2949 | 6:ba3c8cc649d3 | cannam | initialise(). Warning only - DONE |
| 2950 | |||
| 2951 | * Plugin fails gracelessly when constructed with "weird" sample |
||
| 2952 | rate or initialised with "wrong" step size, block size, or |
||
| 2953 | number of channels |
||
| 2954 | 0:f89128a316e7 | cannam | |
| 2955 | Well, that's quite a lot of tests already. What else? |
||
| 2956 | |||
| 2957 | */ |
||
| 2958 | |||
| 2959 | bool good = true; |
||
| 2960 | |||
| 2961 | try {
|
||
| 2962 | |||
| 2963 | 39:07144cdcbedf | Chris | if (m_options & Test::SingleTest) {
|
| 2964 | 4:d8724c5a6d83 | cannam | |
| 2965 | 39:07144cdcbedf | Chris | if (registry().find(m_singleTest) != registry().end()) {
|
| 2966 | |||
| 2967 | good = performTest(m_singleTest, notes, warnings, errors); |
||
| 2968 | |||
| 2969 | } else {
|
||
| 2970 | |||
| 2971 | std::cout << " ** ERROR: Unknown single-test id \"" |
||
| 2972 | << m_singleTest << "\"" << std::endl; |
||
| 2973 | good = false; |
||
| 2974 | } |
||
| 2975 | |||
| 2976 | } else {
|
||
| 2977 | |||
| 2978 | for (Registry::const_iterator i = registry().begin(); |
||
| 2979 | i != registry().end(); ++i) {
|
||
| 2980 | |||
| 2981 | bool thisGood = performTest(i->first, notes, warnings, errors); |
||
| 2982 | if (!thisGood) good = false; |
||
| 2983 | 0:f89128a316e7 | cannam | } |
| 2984 | } |
||
| 2985 | 39:07144cdcbedf | Chris | |
| 2986 | 0:f89128a316e7 | cannam | } catch (Test::FailedToLoadPlugin) {
|
| 2987 | 17:ea8865f488a0 | cannam | std::cout << " ** ERROR: Failed to load plugin (key = \"" << m_key |
| 2988 | 0:f89128a316e7 | cannam | << "\")" << std::endl; |
| 2989 | 25:612333efd521 | cannam | std::cout << " ** NOTE: Vamp plugin path is: " << std::endl; |
| 2990 | std::vector<std::string> pp = PluginHostAdapter::getPluginPath(); |
||
| 2991 | for (size_t i = 0; i < pp.size(); ++i) {
|
||
| 2992 | std::cout << " " << pp[i] << std::endl; |
||
| 2993 | } |
||
| 2994 | 26:eff1772ba397 | cannam | good = false; |
| 2995 | 0:f89128a316e7 | cannam | } |
| 2996 | |||
| 2997 | return good; |
||
| 2998 | } |
||
| 2999 | |||
| 3000 | 39:07144cdcbedf | Chris | bool |
| 3001 | Tester::performTest(std::string id, int ¬es, int &warnings, int &errors) |
||
| 3002 | {
|
||
| 3003 | std::cout << " -- Performing test: " |
||
| 3004 | << id |
||
| 3005 | << " " |
||
| 3006 | << nameIndex()[id] |
||
| 3007 | << std::endl; |
||
| 3008 | |||
| 3009 | Test *test = registry()[id]->makeTest(); |
||
| 3010 | Test::Results results = test->test(m_key, m_options); |
||
| 3011 | delete test; |
||
| 3012 | |||
| 3013 | set<string> printed; |
||
| 3014 | |||
| 3015 | bool good = true; |
||
| 3016 | |||
| 3017 | for (int j = 0; j < (int)results.size(); ++j) {
|
||
| 3018 | string message = results[j].message(); |
||
| 3019 | if (printed.find(message) != printed.end()) continue; |
||
| 3020 | printed.insert(message); |
||
| 3021 | switch (results[j].code()) {
|
||
| 3022 | case Test::Result::Success: |
||
| 3023 | break; |
||
| 3024 | case Test::Result::Note: |
||
| 3025 | std::cout << " ** NOTE: " << results[j].message() << std::endl; |
||
| 3026 | ++notes; |
||
| 3027 | break; |
||
| 3028 | case Test::Result::Warning: |
||
| 3029 | std::cout << " ** WARNING: " << results[j].message() << std::endl; |
||
| 3030 | ++warnings; |
||
| 3031 | break; |
||
| 3032 | case Test::Result::Error: |
||
| 3033 | std::cout << " ** ERROR: " << results[j].message() << std::endl; |
||
| 3034 | ++errors; |
||
| 3035 | good = false; |
||
| 3036 | break; |
||
| 3037 | } |
||
| 3038 | } |
||
| 3039 | |||
| 3040 | return good; |
||
| 3041 | } |
||
| 3042 | |||
| 3043 | 0:f89128a316e7 | cannam | /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ |
| 3044 | |||
| 3045 | /* |
||
| 3046 | Vamp Plugin Tester |
||
| 3047 | Chris Cannam, cannam@all-day-breakfast.com |
||
| 3048 | Centre for Digital Music, Queen Mary, University of London. |
||
| 3049 | 42:f1e8e14e9c96 | Chris | Copyright 2009-2014 QMUL. |
| 3050 | 0:f89128a316e7 | cannam | |
| 3051 | This program loads a Vamp plugin and tests its susceptibility to a |
||
| 3052 | number of common pitfalls, including handling of extremes of input |
||
| 3053 | data. If you can think of any additional useful tests that are |
||
| 3054 | easily added, please send them to me. |
||
| 3055 | |||
| 3056 | Permission is hereby granted, free of charge, to any person |
||
| 3057 | obtaining a copy of this software and associated documentation |
||
| 3058 | files (the "Software"), to deal in the Software without |
||
| 3059 | restriction, including without limitation the rights to use, copy, |
||
| 3060 | modify, merge, publish, distribute, sublicense, and/or sell copies |
||
| 3061 | of the Software, and to permit persons to whom the Software is |
||
| 3062 | furnished to do so, subject to the following conditions: |
||
| 3063 | |||
| 3064 | The above copyright notice and this permission notice shall be |
||
| 3065 | included in all copies or substantial portions of the Software. |
||
| 3066 | |||
| 3067 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
||
| 3068 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
||
| 3069 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
||
| 3070 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR |
||
| 3071 | ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF |
||
| 3072 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
||
| 3073 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
||
| 3074 | |||
| 3075 | Except as contained in this notice, the names of the Centre for |
||
| 3076 | Digital Music; Queen Mary, University of London; and Chris Cannam |
||
| 3077 | shall not be used in advertising or otherwise to promote the sale, |
||
| 3078 | use or other dealings in this Software without prior written |
||
| 3079 | authorization. |
||
| 3080 | */ |
||
| 3081 | |||
| 3082 | #ifndef _TESTER_H_ |
||
| 3083 | #define _TESTER_H_ |
||
| 3084 | |||
| 3085 | #include <string> |
||
| 3086 | |||
| 3087 | #include "Test.h" |
||
| 3088 | |||
| 3089 | class Tester |
||
| 3090 | {
|
||
| 3091 | public: |
||
| 3092 | 39:07144cdcbedf | Chris | Tester(std::string pluginKey, Test::Options, std::string singleTestId = ""); |
| 3093 | 0:f89128a316e7 | cannam | ~Tester(); |
| 3094 | |||
| 3095 | 4:d8724c5a6d83 | cannam | bool test(int ¬es, int &warnings, int &errors); |
| 3096 | 0:f89128a316e7 | cannam | |
| 3097 | 40:649f32c7eb41 | Chris | static void listTests(); |
| 3098 | |||
| 3099 | 0:f89128a316e7 | cannam | class Registrar {
|
| 3100 | public: |
||
| 3101 | 39:07144cdcbedf | Chris | Registrar(std::string id, std::string name) {
|
| 3102 | Tester::registerTest(id, name, this); |
||
| 3103 | } |
||
| 3104 | 15:dde5eb0f1826 | cannam | virtual ~Registrar() { }
|
| 3105 | 0:f89128a316e7 | cannam | virtual Test *makeTest() = 0; |
| 3106 | }; |
||
| 3107 | |||
| 3108 | template <typename T> |
||
| 3109 | class TestRegistrar : Registrar {
|
||
| 3110 | public: |
||
| 3111 | 39:07144cdcbedf | Chris | TestRegistrar(std::string id, std::string name) : |
| 3112 | Registrar(id, name) { }
|
||
| 3113 | 0:f89128a316e7 | cannam | virtual Test *makeTest() { return new T(); }
|
| 3114 | }; |
||
| 3115 | |||
| 3116 | 39:07144cdcbedf | Chris | static void registerTest(std::string id, std::string name, Registrar *r) {
|
| 3117 | nameIndex()[id] = name; |
||
| 3118 | registry()[id] = r; |
||
| 3119 | 0:f89128a316e7 | cannam | } |
| 3120 | |||
| 3121 | protected: |
||
| 3122 | std::string m_key; |
||
| 3123 | 8:3019cb6b538d | cannam | Test::Options m_options; |
| 3124 | 39:07144cdcbedf | Chris | std::string m_singleTest; |
| 3125 | typedef std::map<std::string, std::string> NameIndex; |
||
| 3126 | 0:f89128a316e7 | cannam | typedef std::map<std::string, Registrar *> Registry; |
| 3127 | 39:07144cdcbedf | Chris | static NameIndex &nameIndex(); |
| 3128 | 0:f89128a316e7 | cannam | static Registry ®istry(); |
| 3129 | 39:07144cdcbedf | Chris | |
| 3130 | bool performTest(std::string id, int ¬es, int &warnings, int &errors); |
||
| 3131 | 0:f89128a316e7 | cannam | }; |
| 3132 | |||
| 3133 | #endif |
||
| 3134 | |||
| 3135 | 44:225201df055c | Chris | |
| 3136 | 50:929b95cd25b1 | Chris | TOOLPREFIX ?= i686-w64-mingw32- |
| 3137 | 44:225201df055c | Chris | CXX = $(TOOLPREFIX)g++ |
| 3138 | CC = $(TOOLPREFIX)gcc |
||
| 3139 | LD = $(TOOLPREFIX)g++ |
||
| 3140 | AR = $(TOOLPREFIX)ar |
||
| 3141 | RANLIB = $(TOOLPREFIX)ranlib |
||
| 3142 | |||
| 3143 | 67:fa66ee7dcf08 | Chris | LDFLAGS += -static -L../vamp-plugin-sdk -lvamp-hostsdk -std=gnu++98 |
| 3144 | CXXFLAGS += -I../vamp-plugin-sdk -g -Wall -Wextra -std=gnu++98 |
||
| 3145 | 44:225201df055c | Chris | |
| 3146 | OBJECTS := vamp-plugin-tester.o Tester.o Test.o TestStaticData.o TestInputExtremes.o TestMultipleRuns.o TestOutputs.o TestDefaults.o TestInitialise.o |
||
| 3147 | |||
| 3148 | 45:56eabc22ccbf | Chris | vamp-plugin-tester.exe: $(OBJECTS) |
| 3149 | 44:225201df055c | Chris | $(CXX) $(OBJECTS) -o $@ $(LDFLAGS) |
| 3150 | |||
| 3151 | clean: |
||
| 3152 | rm -f $(OBJECTS) |
||
| 3153 | |||
| 3154 | distclean: clean |
||
| 3155 | 45:56eabc22ccbf | Chris | rm -f *~ vamp-plugin-tester.exe |
| 3156 | 44:225201df055c | Chris | |
| 3157 | depend: |
||
| 3158 | makedepend -Y *.cpp *.h |
||
| 3159 | |||
| 3160 | # DO NOT DELETE |
||
| 3161 | |||
| 3162 | Test.o: Test.h |
||
| 3163 | TestDefaults.o: TestDefaults.h Test.h Tester.h |
||
| 3164 | TestInitialise.o: TestInitialise.h Test.h Tester.h |
||
| 3165 | TestInputExtremes.o: TestInputExtremes.h Test.h Tester.h |
||
| 3166 | TestMultipleRuns.o: TestMultipleRuns.h Test.h Tester.h |
||
| 3167 | TestOutputs.o: TestOutputs.h Test.h Tester.h |
||
| 3168 | TestStaticData.o: TestStaticData.h Test.h Tester.h |
||
| 3169 | Tester.o: Tester.h Test.h |
||
| 3170 | vamp-plugin-tester.o: Tester.h Test.h |
||
| 3171 | TestDefaults.o: Test.h Tester.h |
||
| 3172 | TestInitialise.o: Test.h Tester.h |
||
| 3173 | TestInputExtremes.o: Test.h Tester.h |
||
| 3174 | TestMultipleRuns.o: Test.h Tester.h |
||
| 3175 | TestOutputs.o: Test.h Tester.h |
||
| 3176 | TestStaticData.o: Test.h Tester.h |
||
| 3177 | Tester.o: Test.h |
||
| 3178 | 50:929b95cd25b1 | Chris | |
| 3179 | TOOLPREFIX ?= x86_64-w64-mingw32- |
||
| 3180 | CXX = $(TOOLPREFIX)g++ |
||
| 3181 | CC = $(TOOLPREFIX)gcc |
||
| 3182 | LD = $(TOOLPREFIX)g++ |
||
| 3183 | AR = $(TOOLPREFIX)ar |
||
| 3184 | RANLIB = $(TOOLPREFIX)ranlib |
||
| 3185 | |||
| 3186 | LDFLAGS += -static -L../vamp-plugin-sdk -lvamp-hostsdk |
||
| 3187 | CXXFLAGS += -I../vamp-plugin-sdk -g -Wall -Wextra |
||
| 3188 | |||
| 3189 | OBJECTS := vamp-plugin-tester.o Tester.o Test.o TestStaticData.o TestInputExtremes.o TestMultipleRuns.o TestOutputs.o TestDefaults.o TestInitialise.o |
||
| 3190 | |||
| 3191 | vamp-plugin-tester.exe: $(OBJECTS) |
||
| 3192 | $(CXX) $(OBJECTS) -o $@ $(LDFLAGS) |
||
| 3193 | |||
| 3194 | clean: |
||
| 3195 | rm -f $(OBJECTS) |
||
| 3196 | |||
| 3197 | distclean: clean |
||
| 3198 | rm -f *~ vamp-plugin-tester.exe |
||
| 3199 | |||
| 3200 | depend: |
||
| 3201 | makedepend -Y *.cpp *.h |
||
| 3202 | |||
| 3203 | # DO NOT DELETE |
||
| 3204 | |||
| 3205 | Test.o: Test.h |
||
| 3206 | TestDefaults.o: TestDefaults.h Test.h Tester.h |
||
| 3207 | TestInitialise.o: TestInitialise.h Test.h Tester.h |
||
| 3208 | TestInputExtremes.o: TestInputExtremes.h Test.h Tester.h |
||
| 3209 | TestMultipleRuns.o: TestMultipleRuns.h Test.h Tester.h |
||
| 3210 | TestOutputs.o: TestOutputs.h Test.h Tester.h |
||
| 3211 | TestStaticData.o: TestStaticData.h Test.h Tester.h |
||
| 3212 | Tester.o: Tester.h Test.h |
||
| 3213 | vamp-plugin-tester.o: Tester.h Test.h |
||
| 3214 | TestDefaults.o: Test.h Tester.h |
||
| 3215 | TestInitialise.o: Test.h Tester.h |
||
| 3216 | TestInputExtremes.o: Test.h Tester.h |
||
| 3217 | TestMultipleRuns.o: Test.h Tester.h |
||
| 3218 | TestOutputs.o: Test.h Tester.h |
||
| 3219 | TestStaticData.o: Test.h Tester.h |
||
| 3220 | Tester.o: Test.h |
||
| 3221 | 30:ce9b2b9edfe2 | Chris | |
| 3222 | ARCHFLAGS := -isysroot /Developer/SDKs/MacOSX10.4u.sdk -mmacosx-version-min=10.4 -arch i386 -arch ppc |
||
| 3223 | LDFLAGS += $(ARCHFLAGS) -L../vamp-plugin-sdk -lvamp-hostsdk -ldl |
||
| 3224 | CXXFLAGS += $(ARCHFLAGS) -I../vamp-plugin-sdk -g -Wall -Wextra |
||
| 3225 | |||
| 3226 | OBJECTS := vamp-plugin-tester.o Tester.o Test.o TestStaticData.o TestInputExtremes.o TestMultipleRuns.o TestOutputs.o TestDefaults.o TestInitialise.o |
||
| 3227 | |||
| 3228 | vamp-plugin-tester: $(OBJECTS) |
||
| 3229 | $(CXX) $(OBJECTS) -o $@ $(LDFLAGS) |
||
| 3230 | |||
| 3231 | clean: |
||
| 3232 | rm -f $(OBJECTS) |
||
| 3233 | |||
| 3234 | distclean: clean |
||
| 3235 | rm -f *~ vamp-plugin-tester |
||
| 3236 | |||
| 3237 | depend: |
||
| 3238 | makedepend -Y *.cpp *.h |
||
| 3239 | |||
| 3240 | # DO NOT DELETE |
||
| 3241 | |||
| 3242 | Test.o: Test.h |
||
| 3243 | TestDefaults.o: TestDefaults.h Test.h Tester.h |
||
| 3244 | TestInitialise.o: TestInitialise.h Test.h Tester.h |
||
| 3245 | TestInputExtremes.o: TestInputExtremes.h Test.h Tester.h |
||
| 3246 | TestMultipleRuns.o: TestMultipleRuns.h Test.h Tester.h |
||
| 3247 | TestOutputs.o: TestOutputs.h Test.h Tester.h |
||
| 3248 | TestStaticData.o: TestStaticData.h Test.h Tester.h |
||
| 3249 | Tester.o: Tester.h Test.h |
||
| 3250 | vamp-plugin-tester.o: Tester.h Test.h |
||
| 3251 | TestDefaults.o: Test.h Tester.h |
||
| 3252 | TestInitialise.o: Test.h Tester.h |
||
| 3253 | TestInputExtremes.o: Test.h Tester.h |
||
| 3254 | TestMultipleRuns.o: Test.h Tester.h |
||
| 3255 | TestOutputs.o: Test.h Tester.h |
||
| 3256 | TestStaticData.o: Test.h Tester.h |
||
| 3257 | Tester.o: Test.h |
||
| 3258 | |||
| 3259 | ARCHFLAGS := -isysroot /Developer/SDKs/MacOSX10.4u.sdk -mmacosx-version-min=10.4 -arch x86_64 |
||
| 3260 | LDFLAGS += $(ARCHFLAGS) -L../vamp-plugin-sdk -lvamp-hostsdk -ldl |
||
| 3261 | CXXFLAGS += $(ARCHFLAGS) -I../vamp-plugin-sdk -g -Wall -Wextra |
||
| 3262 | |||
| 3263 | OBJECTS := vamp-plugin-tester.o Tester.o Test.o TestStaticData.o TestInputExtremes.o TestMultipleRuns.o TestOutputs.o TestDefaults.o TestInitialise.o |
||
| 3264 | |||
| 3265 | vamp-plugin-tester-64: $(OBJECTS) |
||
| 3266 | $(CXX) $(OBJECTS) -o $@ $(LDFLAGS) |
||
| 3267 | |||
| 3268 | clean: |
||
| 3269 | rm -f $(OBJECTS) |
||
| 3270 | |||
| 3271 | distclean: clean |
||
| 3272 | rm -f *~ vamp-plugin-tester |
||
| 3273 | |||
| 3274 | depend: |
||
| 3275 | makedepend -Y *.cpp *.h |
||
| 3276 | |||
| 3277 | # DO NOT DELETE |
||
| 3278 | |||
| 3279 | Test.o: Test.h |
||
| 3280 | TestDefaults.o: TestDefaults.h Test.h Tester.h |
||
| 3281 | TestInitialise.o: TestInitialise.h Test.h Tester.h |
||
| 3282 | TestInputExtremes.o: TestInputExtremes.h Test.h Tester.h |
||
| 3283 | TestMultipleRuns.o: TestMultipleRuns.h Test.h Tester.h |
||
| 3284 | TestOutputs.o: TestOutputs.h Test.h Tester.h |
||
| 3285 | TestStaticData.o: TestStaticData.h Test.h Tester.h |
||
| 3286 | Tester.o: Tester.h Test.h |
||
| 3287 | vamp-plugin-tester.o: Tester.h Test.h |
||
| 3288 | TestDefaults.o: Test.h Tester.h |
||
| 3289 | TestInitialise.o: Test.h Tester.h |
||
| 3290 | TestInputExtremes.o: Test.h Tester.h |
||
| 3291 | TestMultipleRuns.o: Test.h Tester.h |
||
| 3292 | TestOutputs.o: Test.h Tester.h |
||
| 3293 | TestStaticData.o: Test.h Tester.h |
||
| 3294 | Tester.o: Test.h |
||
| 3295 | 67:fa66ee7dcf08 | Chris |  |
| 3296 | Microsoft Visual Studio Solution File, Format Version 12.00 |
||
| 3297 | # Visual Studio 14 |
||
| 3298 | VisualStudioVersion = 14.0.25420.1 |
||
| 3299 | MinimumVisualStudioVersion = 10.0.40219.1 |
||
| 3300 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "vamp-plugin-tester", "vamp-plugin-tester.vcxproj", "{5E60958F-4DFC-4F22-B592-381749F60CFF}"
|
||
| 3301 | EndProject |
||
| 3302 | Global |
||
| 3303 | GlobalSection(SolutionConfigurationPlatforms) = preSolution |
||
| 3304 | Debug|x64 = Debug|x64 |
||
| 3305 | Debug|x86 = Debug|x86 |
||
| 3306 | Release|x64 = Release|x64 |
||
| 3307 | Release|x86 = Release|x86 |
||
| 3308 | EndGlobalSection |
||
| 3309 | GlobalSection(ProjectConfigurationPlatforms) = postSolution |
||
| 3310 | {5E60958F-4DFC-4F22-B592-381749F60CFF}.Debug|x64.ActiveCfg = Debug|x64
|
||
| 3311 | {5E60958F-4DFC-4F22-B592-381749F60CFF}.Debug|x64.Build.0 = Debug|x64
|
||
| 3312 | {5E60958F-4DFC-4F22-B592-381749F60CFF}.Debug|x86.ActiveCfg = Debug|Win32
|
||
| 3313 | {5E60958F-4DFC-4F22-B592-381749F60CFF}.Debug|x86.Build.0 = Debug|Win32
|
||
| 3314 | {5E60958F-4DFC-4F22-B592-381749F60CFF}.Release|x64.ActiveCfg = Release|x64
|
||
| 3315 | {5E60958F-4DFC-4F22-B592-381749F60CFF}.Release|x64.Build.0 = Release|x64
|
||
| 3316 | {5E60958F-4DFC-4F22-B592-381749F60CFF}.Release|x86.ActiveCfg = Release|Win32
|
||
| 3317 | {5E60958F-4DFC-4F22-B592-381749F60CFF}.Release|x86.Build.0 = Release|Win32
|
||
| 3318 | EndGlobalSection |
||
| 3319 | GlobalSection(SolutionProperties) = preSolution |
||
| 3320 | HideSolutionNode = FALSE |
||
| 3321 | EndGlobalSection |
||
| 3322 | EndGlobal |
||
| 3323 | <?xml version="1.0" encoding="utf-8"?> |
||
| 3324 | <Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
||
| 3325 | <ItemGroup Label="ProjectConfigurations"> |
||
| 3326 | <ProjectConfiguration Include="Debug|Win32"> |
||
| 3327 | <Configuration>Debug</Configuration> |
||
| 3328 | <Platform>Win32</Platform> |
||
| 3329 | </ProjectConfiguration> |
||
| 3330 | <ProjectConfiguration Include="Release|Win32"> |
||
| 3331 | <Configuration>Release</Configuration> |
||
| 3332 | <Platform>Win32</Platform> |
||
| 3333 | </ProjectConfiguration> |
||
| 3334 | <ProjectConfiguration Include="Debug|x64"> |
||
| 3335 | <Configuration>Debug</Configuration> |
||
| 3336 | <Platform>x64</Platform> |
||
| 3337 | </ProjectConfiguration> |
||
| 3338 | <ProjectConfiguration Include="Release|x64"> |
||
| 3339 | <Configuration>Release</Configuration> |
||
| 3340 | <Platform>x64</Platform> |
||
| 3341 | </ProjectConfiguration> |
||
| 3342 | </ItemGroup> |
||
| 3343 | <PropertyGroup Label="Globals"> |
||
| 3344 | <ProjectGuid>{5E60958F-4DFC-4F22-B592-381749F60CFF}</ProjectGuid>
|
||
| 3345 | <Keyword>Win32Proj</Keyword> |
||
| 3346 | </PropertyGroup> |
||
| 3347 | <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> |
||
| 3348 | <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> |
||
| 3349 | <ConfigurationType>Application</ConfigurationType> |
||
| 3350 | <UseDebugLibraries>true</UseDebugLibraries> |
||
| 3351 | <PlatformToolset>v140</PlatformToolset> |
||
| 3352 | </PropertyGroup> |
||
| 3353 | <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> |
||
| 3354 | <ConfigurationType>Application</ConfigurationType> |
||
| 3355 | <UseDebugLibraries>false</UseDebugLibraries> |
||
| 3356 | <PlatformToolset>v140</PlatformToolset> |
||
| 3357 | </PropertyGroup> |
||
| 3358 | <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration"> |
||
| 3359 | <ConfigurationType>Application</ConfigurationType> |
||
| 3360 | <UseDebugLibraries>true</UseDebugLibraries> |
||
| 3361 | <PlatformToolset>v140</PlatformToolset> |
||
| 3362 | </PropertyGroup> |
||
| 3363 | <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration"> |
||
| 3364 | <ConfigurationType>Application</ConfigurationType> |
||
| 3365 | <UseDebugLibraries>false</UseDebugLibraries> |
||
| 3366 | <PlatformToolset>v140</PlatformToolset> |
||
| 3367 | </PropertyGroup> |
||
| 3368 | <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> |
||
| 3369 | <ImportGroup Label="ExtensionSettings"> |
||
| 3370 | </ImportGroup> |
||
| 3371 | <ImportGroup Label="Shared"> |
||
| 3372 | </ImportGroup> |
||
| 3373 | <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> |
||
| 3374 | <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||
| 3375 | </ImportGroup> |
||
| 3376 | <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> |
||
| 3377 | <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||
| 3378 | </ImportGroup> |
||
| 3379 | <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> |
||
| 3380 | <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||
| 3381 | </ImportGroup> |
||
| 3382 | <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> |
||
| 3383 | <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||
| 3384 | </ImportGroup> |
||
| 3385 | <PropertyGroup Label="UserMacros" /> |
||
| 3386 | <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> |
||
| 3387 | <LinkIncremental>true</LinkIncremental> |
||
| 3388 | </PropertyGroup> |
||
| 3389 | <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> |
||
| 3390 | <LinkIncremental>true</LinkIncremental> |
||
| 3391 | </PropertyGroup> |
||
| 3392 | <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> |
||
| 3393 | <ClCompile> |
||
| 3394 | <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> |
||
| 3395 | <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> |
||
| 3396 | <WarningLevel>Level3</WarningLevel> |
||
| 3397 | <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> |
||
| 3398 | <Optimization>Disabled</Optimization> |
||
| 3399 | </ClCompile> |
||
| 3400 | <Link> |
||
| 3401 | <TargetMachine>MachineX86</TargetMachine> |
||
| 3402 | <GenerateDebugInformation>true</GenerateDebugInformation> |
||
| 3403 | <SubSystem>Console</SubSystem> |
||
| 3404 | </Link> |
||
| 3405 | </ItemDefinitionGroup> |
||
| 3406 | <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> |
||
| 3407 | <ClCompile> |
||
| 3408 | <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;_USE_MATH_DEFINES;%(PreprocessorDefinitions)</PreprocessorDefinitions> |
||
| 3409 | <RuntimeLibrary>MultiThreaded</RuntimeLibrary> |
||
| 3410 | <WarningLevel>Level3</WarningLevel> |
||
| 3411 | <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> |
||
| 3412 | 71:c4d84af6b609 | Chris | <AdditionalIncludeDirectories>$(ProjectDir)\..\vamp-plugin-sdk;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> |
| 3413 | 67:fa66ee7dcf08 | Chris | </ClCompile> |
| 3414 | <Link> |
||
| 3415 | <TargetMachine>MachineX86</TargetMachine> |
||
| 3416 | <GenerateDebugInformation>true</GenerateDebugInformation> |
||
| 3417 | <SubSystem>Console</SubSystem> |
||
| 3418 | <EnableCOMDATFolding>true</EnableCOMDATFolding> |
||
| 3419 | <OptimizeReferences>true</OptimizeReferences> |
||
| 3420 | </Link> |
||
| 3421 | </ItemDefinitionGroup> |
||
| 3422 | <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> |
||
| 3423 | <ClCompile> |
||
| 3424 | 71:c4d84af6b609 | Chris | <AdditionalIncludeDirectories>$(ProjectDir)\..\vamp-plugin-sdk;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> |
| 3425 | 67:fa66ee7dcf08 | Chris | <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;_USE_MATH_DEFINES;%(PreprocessorDefinitions)</PreprocessorDefinitions> |
| 3426 | <RuntimeLibrary>MultiThreaded</RuntimeLibrary> |
||
| 3427 | </ClCompile> |
||
| 3428 | </ItemDefinitionGroup> |
||
| 3429 | <ItemGroup> |
||
| 3430 | <ClCompile Include="..\Test.cpp" /> |
||
| 3431 | <ClCompile Include="..\TestDefaults.cpp" /> |
||
| 3432 | <ClCompile Include="..\Tester.cpp" /> |
||
| 3433 | <ClCompile Include="..\TestInitialise.cpp" /> |
||
| 3434 | <ClCompile Include="..\TestInputExtremes.cpp" /> |
||
| 3435 | <ClCompile Include="..\TestMultipleRuns.cpp" /> |
||
| 3436 | <ClCompile Include="..\TestOutputs.cpp" /> |
||
| 3437 | <ClCompile Include="..\TestStaticData.cpp" /> |
||
| 3438 | <ClCompile Include="..\vamp-plugin-sdk\src\vamp-hostsdk\Files.cpp" /> |
||
| 3439 | <ClCompile Include="..\vamp-plugin-sdk\src\vamp-hostsdk\PluginBufferingAdapter.cpp" /> |
||
| 3440 | <ClCompile Include="..\vamp-plugin-sdk\src\vamp-hostsdk\PluginChannelAdapter.cpp" /> |
||
| 3441 | <ClCompile Include="..\vamp-plugin-sdk\src\vamp-hostsdk\PluginHostAdapter.cpp" /> |
||
| 3442 | <ClCompile Include="..\vamp-plugin-sdk\src\vamp-hostsdk\PluginInputDomainAdapter.cpp" /> |
||
| 3443 | <ClCompile Include="..\vamp-plugin-sdk\src\vamp-hostsdk\PluginLoader.cpp" /> |
||
| 3444 | <ClCompile Include="..\vamp-plugin-sdk\src\vamp-hostsdk\PluginSummarisingAdapter.cpp" /> |
||
| 3445 | <ClCompile Include="..\vamp-plugin-sdk\src\vamp-hostsdk\PluginWrapper.cpp" /> |
||
| 3446 | <ClCompile Include="..\vamp-plugin-sdk\src\vamp-hostsdk\RealTime.cpp" /> |
||
| 3447 | <ClCompile Include="..\vamp-plugin-sdk\src\vamp-sdk\FFT.cpp" /> |
||
| 3448 | <ClCompile Include="..\vamp-plugin-tester.cpp" /> |
||
| 3449 | </ItemGroup> |
||
| 3450 | <ItemGroup> |
||
| 3451 | <ClInclude Include="..\Test.h" /> |
||
| 3452 | <ClInclude Include="..\TestDefaults.h" /> |
||
| 3453 | <ClInclude Include="..\Tester.h" /> |
||
| 3454 | <ClInclude Include="..\TestInitialise.h" /> |
||
| 3455 | <ClInclude Include="..\TestInputExtremes.h" /> |
||
| 3456 | <ClInclude Include="..\TestMultipleRuns.h" /> |
||
| 3457 | <ClInclude Include="..\TestOutputs.h" /> |
||
| 3458 | <ClInclude Include="..\TestStaticData.h" /> |
||
| 3459 | <ClInclude Include="..\vamp-plugin-sdk\examples\AmplitudeFollower.h" /> |
||
| 3460 | <ClInclude Include="..\vamp-plugin-sdk\examples\FixedTempoEstimator.h" /> |
||
| 3461 | <ClInclude Include="..\vamp-plugin-sdk\examples\PercussionOnsetDetector.h" /> |
||
| 3462 | <ClInclude Include="..\vamp-plugin-sdk\examples\PowerSpectrum.h" /> |
||
| 3463 | <ClInclude Include="..\vamp-plugin-sdk\examples\SpectralCentroid.h" /> |
||
| 3464 | <ClInclude Include="..\vamp-plugin-sdk\examples\ZeroCrossing.h" /> |
||
| 3465 | <ClInclude Include="..\vamp-plugin-sdk\host\system.h" /> |
||
| 3466 | <ClInclude Include="..\vamp-plugin-sdk\skeleton\MyPlugin.h" /> |
||
| 3467 | <ClInclude Include="..\vamp-plugin-sdk\src\vamp-hostsdk\Files.h" /> |
||
| 3468 | <ClInclude Include="..\vamp-plugin-sdk\src\vamp-hostsdk\Window.h" /> |
||
| 3469 | <ClInclude Include="..\vamp-plugin-sdk\src\vamp-sdk\ext\kiss_fft.h" /> |
||
| 3470 | <ClInclude Include="..\vamp-plugin-sdk\src\vamp-sdk\ext\kiss_fftr.h" /> |
||
| 3471 | <ClInclude Include="..\vamp-plugin-sdk\src\vamp-sdk\ext\_kiss_fft_guts.h" /> |
||
| 3472 | <ClInclude Include="..\vamp-plugin-sdk\vamp-hostsdk\host-c.h" /> |
||
| 3473 | <ClInclude Include="..\vamp-plugin-sdk\vamp-hostsdk\hostguard.h" /> |
||
| 3474 | <ClInclude Include="..\vamp-plugin-sdk\vamp-hostsdk\Plugin.h" /> |
||
| 3475 | <ClInclude Include="..\vamp-plugin-sdk\vamp-hostsdk\PluginBase.h" /> |
||
| 3476 | <ClInclude Include="..\vamp-plugin-sdk\vamp-hostsdk\PluginBufferingAdapter.h" /> |
||
| 3477 | <ClInclude Include="..\vamp-plugin-sdk\vamp-hostsdk\PluginChannelAdapter.h" /> |
||
| 3478 | <ClInclude Include="..\vamp-plugin-sdk\vamp-hostsdk\PluginHostAdapter.h" /> |
||
| 3479 | <ClInclude Include="..\vamp-plugin-sdk\vamp-hostsdk\PluginInputDomainAdapter.h" /> |
||
| 3480 | <ClInclude Include="..\vamp-plugin-sdk\vamp-hostsdk\PluginLoader.h" /> |
||
| 3481 | <ClInclude Include="..\vamp-plugin-sdk\vamp-hostsdk\PluginSummarisingAdapter.h" /> |
||
| 3482 | <ClInclude Include="..\vamp-plugin-sdk\vamp-hostsdk\PluginWrapper.h" /> |
||
| 3483 | <ClInclude Include="..\vamp-plugin-sdk\vamp-hostsdk\RealTime.h" /> |
||
| 3484 | <ClInclude Include="..\vamp-plugin-sdk\vamp-hostsdk\vamp-hostsdk.h" /> |
||
| 3485 | <ClInclude Include="..\vamp-plugin-sdk\vamp-sdk\FFT.h" /> |
||
| 3486 | <ClInclude Include="..\vamp-plugin-sdk\vamp-sdk\plugguard.h" /> |
||
| 3487 | <ClInclude Include="..\vamp-plugin-sdk\vamp-sdk\Plugin.h" /> |
||
| 3488 | <ClInclude Include="..\vamp-plugin-sdk\vamp-sdk\PluginAdapter.h" /> |
||
| 3489 | <ClInclude Include="..\vamp-plugin-sdk\vamp-sdk\PluginBase.h" /> |
||
| 3490 | <ClInclude Include="..\vamp-plugin-sdk\vamp-sdk\RealTime.h" /> |
||
| 3491 | <ClInclude Include="..\vamp-plugin-sdk\vamp-sdk\vamp-sdk.h" /> |
||
| 3492 | <ClInclude Include="..\vamp-plugin-sdk\vamp\vamp.h" /> |
||
| 3493 | </ItemGroup> |
||
| 3494 | <ItemGroup> |
||
| 3495 | <None Include="..\vamp-plugin-sdk\rdf\doc\glance.htm" /> |
||
| 3496 | <None Include="..\vamp-plugin-sdk\rdf\doc\vamp.html" /> |
||
| 3497 | <None Include="..\vamp-plugin-sdk\skeleton\Makefile.inc" /> |
||
| 3498 | </ItemGroup> |
||
| 3499 | <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> |
||
| 3500 | <ImportGroup Label="ExtensionTargets"> |
||
| 3501 | </ImportGroup> |
||
| 3502 | </Project> |
||
| 3503 | 70:2d3e1d1f99c0 | Chris | #!/bin/bash |
| 3504 | |||
| 3505 | # Disable shellcheck warnings for useless-use-of-cat. UUOC is good |
||
| 3506 | # practice, not bad: clearer, safer, less error-prone. |
||
| 3507 | # shellcheck disable=SC2002 |
||
| 3508 | |||
| 3509 | 76:a6c9a0ca493e | Chris | sml="$REPOINT_SML" |
| 3510 | 70:2d3e1d1f99c0 | Chris | |
| 3511 | set -eu |
||
| 3512 | |||
| 3513 | 76:a6c9a0ca493e | Chris | # avoid gussying up output |
| 3514 | export HGPLAIN=true |
||
| 3515 | |||
| 3516 | 70:2d3e1d1f99c0 | Chris | mydir=$(dirname "$0") |
| 3517 | 76:a6c9a0ca493e | Chris | program="$mydir/repoint.sml" |
| 3518 | 70:2d3e1d1f99c0 | Chris | |
| 3519 | hasher= |
||
| 3520 | local_install= |
||
| 3521 | if [ -w "$mydir" ]; then |
||
| 3522 | if echo | sha256sum >/dev/null 2>&1 ; then |
||
| 3523 | hasher=sha256sum |
||
| 3524 | local_install=true |
||
| 3525 | elif echo | shasum >/dev/null 2>&1 ; then |
||
| 3526 | hasher=shasum |
||
| 3527 | local_install=true |
||
| 3528 | else |
||
| 3529 | echo "WARNING: sha256sum or shasum program not found" 1>&2 |
||
| 3530 | fi |
||
| 3531 | fi |
||
| 3532 | |||
| 3533 | if [ -n "$local_install" ]; then |
||
| 3534 | hash=$(echo "$sml" | cat "$program" - | $hasher | cut -c1-16) |
||
| 3535 | 76:a6c9a0ca493e | Chris | gen_sml=$mydir/.repoint-$hash.sml |
| 3536 | gen_out=$mydir/.repoint-$hash.bin |
||
| 3537 | 70:2d3e1d1f99c0 | Chris | trap 'rm -f $gen_sml' 0 |
| 3538 | else |
||
| 3539 | 76:a6c9a0ca493e | Chris | gen_sml=$(mktemp /tmp/repoint-XXXXXXXX.sml) |
| 3540 | gen_out=$(mktemp /tmp/repoint-XXXXXXXX.bin) |
||
| 3541 | 70:2d3e1d1f99c0 | Chris | trap 'rm -f $gen_sml $gen_out' 0 |
| 3542 | fi |
||
| 3543 | |||
| 3544 | if [ -x "$gen_out" ]; then |
||
| 3545 | exec "$gen_out" "$@" |
||
| 3546 | fi |
||
| 3547 | |||
| 3548 | 76:a6c9a0ca493e | Chris | # We need one of Poly/ML, SML/NJ, MLton, or MLKit. Since we're running |
| 3549 | # a single-file SML program as if it were a script, our order of |
||
| 3550 | # preference is usually based on startup speed. An exception is the |
||
| 3551 | # local_install case, where we retain a persistent binary |
||
| 3552 | 70:2d3e1d1f99c0 | Chris | |
| 3553 | if [ -z "$sml" ]; then |
||
| 3554 | if [ -n "$local_install" ] && mlton 2>&1 | grep -q 'MLton'; then |
||
| 3555 | sml="mlton" |
||
| 3556 | elif sml -h 2>&1 | grep -q 'Standard ML of New Jersey'; then |
||
| 3557 | sml="smlnj" |
||
| 3558 | # We would prefer Poly/ML to SML/NJ, except that Poly v5.7 has a |
||
| 3559 | # nasty bug that occasionally causes it to deadlock on startup. |
||
| 3560 | 76:a6c9a0ca493e | Chris | # That is fixed in v5.7.1, so we could promote it up the order |
| 3561 | # again at some point in future |
||
| 3562 | 70:2d3e1d1f99c0 | Chris | elif echo | poly -v 2>/dev/null | grep -q 'Poly/ML'; then |
| 3563 | sml="poly" |
||
| 3564 | elif mlton 2>&1 | grep -q 'MLton'; then |
||
| 3565 | sml="mlton" |
||
| 3566 | 76:a6c9a0ca493e | Chris | # MLKit is at the bottom because it leaves compiled files around |
| 3567 | # in an MLB subdir in the current directory |
||
| 3568 | elif mlkit 2>&1 | grep -q 'MLKit'; then |
||
| 3569 | sml="mlkit" |
||
| 3570 | 70:2d3e1d1f99c0 | Chris | else cat 1>&2 <<EOF |
| 3571 | |||
| 3572 | ERROR: No supported SML compiler or interpreter found |
||
| 3573 | EOF |
||
| 3574 | cat 1>&2 <<EOF |
||
| 3575 | |||
| 3576 | 76:a6c9a0ca493e | Chris | The Repoint external source code manager needs a Standard ML (SML) |
| 3577 | 70:2d3e1d1f99c0 | Chris | compiler or interpreter to run. |
| 3578 | |||
| 3579 | Please ensure you have one of the following SML implementations |
||
| 3580 | installed and present in your PATH, and try again. |
||
| 3581 | |||
| 3582 | 1. Standard ML of New Jersey |
||
| 3583 | 76:a6c9a0ca493e | Chris | - may be found in a distribution package called: smlnj |
| 3584 | 70:2d3e1d1f99c0 | Chris | - executable name: sml |
| 3585 | |||
| 3586 | 2. Poly/ML |
||
| 3587 | 76:a6c9a0ca493e | Chris | - may be found in a distribution package called: polyml |
| 3588 | 70:2d3e1d1f99c0 | Chris | - executable name: poly |
| 3589 | |||
| 3590 | 3. MLton |
||
| 3591 | 76:a6c9a0ca493e | Chris | - may be found in a distribution package called: mlton |
| 3592 | 70:2d3e1d1f99c0 | Chris | - executable name: mlton |
| 3593 | |||
| 3594 | 76:a6c9a0ca493e | Chris | 4. MLKit |
| 3595 | - may be found in a distribution package called: mlkit |
||
| 3596 | - executable name: mlkit |
||
| 3597 | |||
| 3598 | 70:2d3e1d1f99c0 | Chris | EOF |
| 3599 | exit 2 |
||
| 3600 | fi |
||
| 3601 | fi |
||
| 3602 | |||
| 3603 | arglist="" |
||
| 3604 | for arg in "$@"; do |
||
| 3605 | if [ -n "$arglist" ]; then arglist="$arglist,"; fi |
||
| 3606 | if echo "$arg" | grep -q '["'"'"']' ; then |
||
| 3607 | arglist="$arglist\"usage\"" |
||
| 3608 | else |
||
| 3609 | arglist="$arglist\"$arg\"" |
||
| 3610 | fi |
||
| 3611 | done |
||
| 3612 | |||
| 3613 | case "$sml" in |
||
| 3614 | poly) |
||
| 3615 | if [ -n "$local_install" ] && polyc --help >/dev/null 2>&1 ; then |
||
| 3616 | if [ ! -x "$gen_out" ]; then |
||
| 3617 | polyc -o "$gen_out" "$program" |
||
| 3618 | fi |
||
| 3619 | "$gen_out" "$@" |
||
| 3620 | else |
||
| 3621 | 76:a6c9a0ca493e | Chris | echo 'use "'"$program"'"; repoint ['"$arglist"'];' | |
| 3622 | 70:2d3e1d1f99c0 | Chris | poly -q --error-exit |
| 3623 | fi ;; |
||
| 3624 | mlton) |
||
| 3625 | if [ ! -x "$gen_out" ]; then |
||
| 3626 | 76:a6c9a0ca493e | Chris | echo "[Precompiling Repoint binary...]" 1>&2 |
| 3627 | 70:2d3e1d1f99c0 | Chris | echo "val _ = main ()" | cat "$program" - > "$gen_sml" |
| 3628 | mlton -output "$gen_out" "$gen_sml" |
||
| 3629 | fi |
||
| 3630 | "$gen_out" "$@" ;; |
||
| 3631 | 76:a6c9a0ca493e | Chris | mlkit) |
| 3632 | if [ ! -x "$gen_out" ]; then |
||
| 3633 | echo "[Precompiling Repoint binary...]" 1>&2 |
||
| 3634 | echo "val _ = main ()" | cat "$program" - > "$gen_sml" |
||
| 3635 | mlkit -output "$gen_out" "$gen_sml" |
||
| 3636 | fi |
||
| 3637 | "$gen_out" "$@" ;; |
||
| 3638 | 70:2d3e1d1f99c0 | Chris | smlnj) |
| 3639 | cat "$program" | ( |
||
| 3640 | cat <<EOF |
||
| 3641 | val smlrun__cp = |
||
| 3642 | let val x = !Control.Print.out in |
||
| 3643 | Control.Print.out := { say = fn _ => (), flush = fn () => () };
|
||
| 3644 | x |
||
| 3645 | end; |
||
| 3646 | val smlrun__prev = ref ""; |
||
| 3647 | Control.Print.out := {
|
||
| 3648 | say = fn s => |
||
| 3649 | (if String.isSubstring " Error" s |
||
| 3650 | then (Control.Print.out := smlrun__cp; |
||
| 3651 | (#say smlrun__cp) (!smlrun__prev); |
||
| 3652 | (#say smlrun__cp) s) |
||
| 3653 | else (smlrun__prev := s; ())), |
||
| 3654 | flush = fn s => () |
||
| 3655 | }; |
||
| 3656 | EOF |
||
| 3657 | cat - |
||
| 3658 | cat <<EOF |
||
| 3659 | 76:a6c9a0ca493e | Chris | val _ = repoint [$arglist]; |
| 3660 | 70:2d3e1d1f99c0 | Chris | val _ = OS.Process.exit (OS.Process.success); |
| 3661 | EOF |
||
| 3662 | ) > "$gen_sml" |
||
| 3663 | CM_VERBOSE=false sml "$gen_sml" ;; |
||
| 3664 | *) |
||
| 3665 | echo "ERROR: Unknown SML implementation name: $sml" 1>&2; |
||
| 3666 | exit 2 ;; |
||
| 3667 | esac |
||
| 3668 | |||
| 3669 | {
|
||
| 3670 | "libraries": {
|
||
| 3671 | "vamp-plugin-sdk": {
|
||
| 3672 | 77:2b2f3b45e8ea | Chris | "pin": "328cb056da44" |
| 3673 | 70:2d3e1d1f99c0 | Chris | } |
| 3674 | } |
||
| 3675 | } |
||
| 3676 | {
|
||
| 3677 | "config": {
|
||
| 3678 | "extdir": "." |
||
| 3679 | }, |
||
| 3680 | "services": {
|
||
| 3681 | "soundsoftware": {
|
||
| 3682 | "vcs": ["hg", "git"], |
||
| 3683 | "anonymous": "https://code.soundsoftware.ac.uk/{vcs}/{repository}",
|
||
| 3684 | "authenticated": "https://{account}@code.soundsoftware.ac.uk/{vcs}/{repository}"
|
||
| 3685 | } |
||
| 3686 | }, |
||
| 3687 | "libraries": {
|
||
| 3688 | "vamp-plugin-sdk": {
|
||
| 3689 | "vcs": "hg", |
||
| 3690 | "service": "soundsoftware" |
||
| 3691 | } |
||
| 3692 | } |
||
| 3693 | } |
||
| 3694 | |||
| 3695 | @echo off |
||
| 3696 | PowerShell -NoProfile -ExecutionPolicy Bypass -Command "& '%~dpn0.ps1' %*"; |
||
| 3697 | |||
| 3698 | <# |
||
| 3699 | |||
| 3700 | .SYNOPSIS |
||
| 3701 | A simple manager for third-party source code dependencies. |
||
| 3702 | 76:a6c9a0ca493e | Chris | Run "repoint help" for more documentation. |
| 3703 | 70:2d3e1d1f99c0 | Chris | |
| 3704 | #> |
||
| 3705 | |||
| 3706 | Set-StrictMode -Version 2.0 |
||
| 3707 | $ErrorActionPreference = "Stop" |
||
| 3708 | 76:a6c9a0ca493e | Chris | $env:HGPLAIN = "true" |
| 3709 | 70:2d3e1d1f99c0 | Chris | |
| 3710 | 76:a6c9a0ca493e | Chris | $sml = $env:REPOINT_SML |
| 3711 | 70:2d3e1d1f99c0 | Chris | |
| 3712 | $mydir = Split-Path $MyInvocation.MyCommand.Path -Parent |
||
| 3713 | 76:a6c9a0ca493e | Chris | $program = "$mydir/repoint.sml" |
| 3714 | 70:2d3e1d1f99c0 | Chris | |
| 3715 | # We need either Poly/ML or SML/NJ. No great preference as to which. |
||
| 3716 | |||
| 3717 | 76:a6c9a0ca493e | Chris | # Typical locations |
| 3718 | $env:PATH = "$env:PATH;C:\Program Files (x86)\SMLNJ\bin;C:\Program Files\Poly ML;C:\Program Files (x86)\Poly ML" |
||
| 3719 | |||
| 3720 | 70:2d3e1d1f99c0 | Chris | if (!$sml) {
|
| 3721 | if (Get-Command "sml" -ErrorAction SilentlyContinue) {
|
||
| 3722 | $sml = "smlnj" |
||
| 3723 | } elseif (Get-Command "polyml" -ErrorAction SilentlyContinue) {
|
||
| 3724 | $sml = "poly" |
||
| 3725 | } else {
|
||
| 3726 | echo @" |
||
| 3727 | |||
| 3728 | ERROR: No supported SML compiler or interpreter found |
||
| 3729 | |||
| 3730 | 76:a6c9a0ca493e | Chris | The Repoint external source code manager needs a Standard ML (SML) |
| 3731 | 70:2d3e1d1f99c0 | Chris | compiler or interpreter to run. |
| 3732 | |||
| 3733 | Please ensure you have one of the following SML implementations |
||
| 3734 | installed and present in your PATH, and try again. |
||
| 3735 | |||
| 3736 | 1. Standard ML of New Jersey |
||
| 3737 | - executable name: sml |
||
| 3738 | |||
| 3739 | 2. Poly/ML |
||
| 3740 | - executable name: polyml |
||
| 3741 | |||
| 3742 | "@ |
||
| 3743 | exit 1 |
||
| 3744 | } |
||
| 3745 | } |
||
| 3746 | |||
| 3747 | if ($args -match "'""") {
|
||
| 3748 | $arglist = '["usage"]' |
||
| 3749 | } else {
|
||
| 3750 | $arglist = '["' + ($args -join '","') + '"]' |
||
| 3751 | } |
||
| 3752 | |||
| 3753 | if ($sml -eq "poly") {
|
||
| 3754 | |||
| 3755 | $program = $program -replace "\\","\\\\" |
||
| 3756 | 76:a6c9a0ca493e | Chris | echo "use ""$program""; repoint $arglist" | polyml -q --error-exit | Out-Host |
| 3757 | 70:2d3e1d1f99c0 | Chris | |
| 3758 | if (-not $?) {
|
||
| 3759 | exit $LastExitCode |
||
| 3760 | } |
||
| 3761 | |||
| 3762 | } elseif ($sml -eq "smlnj") {
|
||
| 3763 | |||
| 3764 | $lines = @(Get-Content $program) |
||
| 3765 | $lines = $lines -notmatch "val _ = main ()" |
||
| 3766 | |||
| 3767 | $intro = @" |
||
| 3768 | val smlrun__cp = |
||
| 3769 | let val x = !Control.Print.out in |
||
| 3770 | Control.Print.out := { say = fn _ => (), flush = fn () => () };
|
||
| 3771 | x |
||
| 3772 | end; |
||
| 3773 | val smlrun__prev = ref ""; |
||
| 3774 | Control.Print.out := {
|
||
| 3775 | say = fn s => |
||
| 3776 | (if String.isSubstring "Error" s orelse String.isSubstring "Fail" s |
||
| 3777 | then (Control.Print.out := smlrun__cp; |
||
| 3778 | (#say smlrun__cp) (!smlrun__prev); |
||
| 3779 | (#say smlrun__cp) s) |
||
| 3780 | else (smlrun__prev := s; ())), |
||
| 3781 | flush = fn s => () |
||
| 3782 | }; |
||
| 3783 | "@ -split "[\r\n]+" |
||
| 3784 | |||
| 3785 | $outro = @" |
||
| 3786 | 76:a6c9a0ca493e | Chris | val _ = repoint $arglist; |
| 3787 | 70:2d3e1d1f99c0 | Chris | val _ = OS.Process.exit (OS.Process.success); |
| 3788 | "@ -split "[\r\n]+" |
||
| 3789 | |||
| 3790 | $script = @() |
||
| 3791 | $script += $intro |
||
| 3792 | $script += $lines |
||
| 3793 | $script += $outro |
||
| 3794 | |||
| 3795 | $tmpfile = ([System.IO.Path]::GetTempFileName()) -replace "[.]tmp",".sml" |
||
| 3796 | |||
| 3797 | $script | Out-File -Encoding "ASCII" $tmpfile |
||
| 3798 | |||
| 3799 | $env:CM_VERBOSE="false" |
||
| 3800 | |||
| 3801 | sml $tmpfile |
||
| 3802 | |||
| 3803 | if (-not $?) {
|
||
| 3804 | del $tmpfile |
||
| 3805 | exit $LastExitCode |
||
| 3806 | } |
||
| 3807 | |||
| 3808 | del $tmpfile |
||
| 3809 | |||
| 3810 | } else {
|
||
| 3811 | |||
| 3812 | "Unknown SML implementation name: $sml" |
||
| 3813 | exit 2 |
||
| 3814 | } |
||
| 3815 | (* |
||
| 3816 | DO NOT EDIT THIS FILE. |
||
| 3817 | This file is automatically generated from the individual |
||
| 3818 | 76:a6c9a0ca493e | Chris | source files in the Repoint repository. |
| 3819 | 70:2d3e1d1f99c0 | Chris | *) |
| 3820 | |||
| 3821 | (* |
||
| 3822 | 76:a6c9a0ca493e | Chris | Repoint |
| 3823 | 70:2d3e1d1f99c0 | Chris | |
| 3824 | A simple manager for third-party source code dependencies |
||
| 3825 | |||
| 3826 | Copyright 2018 Chris Cannam, Particular Programs Ltd, |
||
| 3827 | and Queen Mary, University of London |
||
| 3828 | |||
| 3829 | Permission is hereby granted, free of charge, to any person |
||
| 3830 | obtaining a copy of this software and associated documentation |
||
| 3831 | files (the "Software"), to deal in the Software without |
||
| 3832 | restriction, including without limitation the rights to use, copy, |
||
| 3833 | modify, merge, publish, distribute, sublicense, and/or sell copies |
||
| 3834 | of the Software, and to permit persons to whom the Software is |
||
| 3835 | furnished to do so, subject to the following conditions: |
||
| 3836 | |||
| 3837 | The above copyright notice and this permission notice shall be |
||
| 3838 | included in all copies or substantial portions of the Software. |
||
| 3839 | |||
| 3840 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
||
| 3841 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
||
| 3842 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
||
| 3843 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR |
||
| 3844 | ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF |
||
| 3845 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
||
| 3846 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
||
| 3847 | |||
| 3848 | Except as contained in this notice, the names of Chris Cannam, |
||
| 3849 | Particular Programs Ltd, and Queen Mary, University of London |
||
| 3850 | shall not be used in advertising or otherwise to promote the sale, |
||
| 3851 | use or other dealings in this Software without prior written |
||
| 3852 | authorization. |
||
| 3853 | *) |
||
| 3854 | |||
| 3855 | 76:a6c9a0ca493e | Chris | val repoint_version = "0.9.98" |
| 3856 | 70:2d3e1d1f99c0 | Chris | |
| 3857 | |||
| 3858 | datatype vcs = |
||
| 3859 | HG | |
||
| 3860 | GIT | |
||
| 3861 | SVN |
||
| 3862 | |||
| 3863 | datatype source = |
||
| 3864 | URL_SOURCE of string | |
||
| 3865 | SERVICE_SOURCE of {
|
||
| 3866 | service : string, |
||
| 3867 | owner : string option, |
||
| 3868 | repo : string option |
||
| 3869 | } |
||
| 3870 | |||
| 3871 | type id_or_tag = string |
||
| 3872 | |||
| 3873 | datatype pin = |
||
| 3874 | UNPINNED | |
||
| 3875 | PINNED of id_or_tag |
||
| 3876 | |||
| 3877 | datatype libstate = |
||
| 3878 | ABSENT | |
||
| 3879 | CORRECT | |
||
| 3880 | SUPERSEDED | |
||
| 3881 | WRONG |
||
| 3882 | |||
| 3883 | datatype localstate = |
||
| 3884 | MODIFIED | |
||
| 3885 | LOCK_MISMATCHED | |
||
| 3886 | CLEAN |
||
| 3887 | |||
| 3888 | datatype branch = |
||
| 3889 | BRANCH of string | |
||
| 3890 | DEFAULT_BRANCH |
||
| 3891 | |||
| 3892 | (* If we can recover from an error, for example by reporting failure |
||
| 3893 | for this one thing and going on to the next thing, then the error |
||
| 3894 | should usually be returned through a result type rather than an |
||
| 3895 | exception. *) |
||
| 3896 | |||
| 3897 | datatype 'a result = |
||
| 3898 | OK of 'a | |
||
| 3899 | ERROR of string |
||
| 3900 | |||
| 3901 | type libname = string |
||
| 3902 | |||
| 3903 | type libspec = {
|
||
| 3904 | libname : libname, |
||
| 3905 | vcs : vcs, |
||
| 3906 | source : source, |
||
| 3907 | branch : branch, |
||
| 3908 | project_pin : pin, |
||
| 3909 | lock_pin : pin |
||
| 3910 | } |
||
| 3911 | |||
| 3912 | type lock = {
|
||
| 3913 | libname : libname, |
||
| 3914 | id_or_tag : id_or_tag |
||
| 3915 | } |
||
| 3916 | |||
| 3917 | type remote_spec = {
|
||
| 3918 | anon : string option, |
||
| 3919 | auth : string option |
||
| 3920 | } |
||
| 3921 | |||
| 3922 | type provider = {
|
||
| 3923 | service : string, |
||
| 3924 | supports : vcs list, |
||
| 3925 | remote_spec : remote_spec |
||
| 3926 | } |
||
| 3927 | |||
| 3928 | type account = {
|
||
| 3929 | service : string, |
||
| 3930 | login : string |
||
| 3931 | } |
||
| 3932 | |||
| 3933 | type context = {
|
||
| 3934 | rootpath : string, |
||
| 3935 | extdir : string, |
||
| 3936 | providers : provider list, |
||
| 3937 | accounts : account list |
||
| 3938 | } |
||
| 3939 | |||
| 3940 | type userconfig = {
|
||
| 3941 | providers : provider list, |
||
| 3942 | accounts : account list |
||
| 3943 | } |
||
| 3944 | |||
| 3945 | type project = {
|
||
| 3946 | context : context, |
||
| 3947 | libs : libspec list |
||
| 3948 | } |
||
| 3949 | |||
| 3950 | 76:a6c9a0ca493e | Chris | structure RepointFilenames = struct |
| 3951 | val project_file = "repoint-project.json" |
||
| 3952 | val project_lock_file = "repoint-lock.json" |
||
| 3953 | val user_config_file = ".repoint.json" |
||
| 3954 | val archive_dir = ".repoint-archive" |
||
| 3955 | 70:2d3e1d1f99c0 | Chris | end |
| 3956 | |||
| 3957 | signature VCS_CONTROL = sig |
||
| 3958 | |||
| 3959 | 76:a6c9a0ca493e | Chris | (** Check whether the given VCS is installed and working *) |
| 3960 | val is_working : context -> bool result |
||
| 3961 | |||
| 3962 | 70:2d3e1d1f99c0 | Chris | (** Test whether the library is present locally at all *) |
| 3963 | val exists : context -> libname -> bool result |
||
| 3964 | |||
| 3965 | (** Return the id (hash) of the current revision for the library *) |
||
| 3966 | val id_of : context -> libname -> id_or_tag result |
||
| 3967 | |||
| 3968 | (** Test whether the library is at the given id *) |
||
| 3969 | val is_at : context -> libname * id_or_tag -> bool result |
||
| 3970 | |||
| 3971 | (** Test whether the library is on the given branch, i.e. is at |
||
| 3972 | the branch tip or an ancestor of it *) |
||
| 3973 | val is_on_branch : context -> libname * branch -> bool result |
||
| 3974 | |||
| 3975 | (** Test whether the library is at the newest revision for the |
||
| 3976 | given branch. False may indicate that the branch has advanced |
||
| 3977 | or that the library is not on the branch at all. This function |
||
| 3978 | may use the network to check for new revisions *) |
||
| 3979 | val is_newest : context -> libname * source * branch -> bool result |
||
| 3980 | |||
| 3981 | (** Test whether the library is at the newest revision available |
||
| 3982 | locally for the given branch. False may indicate that the |
||
| 3983 | branch has advanced or that the library is not on the branch |
||
| 3984 | at all. This function must not use the network *) |
||
| 3985 | val is_newest_locally : context -> libname * branch -> bool result |
||
| 3986 | |||
| 3987 | (** Test whether the library has been modified in the local |
||
| 3988 | working copy *) |
||
| 3989 | val is_modified_locally : context -> libname -> bool result |
||
| 3990 | |||
| 3991 | (** Check out, i.e. clone a fresh copy of, the repo for the given |
||
| 3992 | library on the given branch *) |
||
| 3993 | val checkout : context -> libname * source * branch -> unit result |
||
| 3994 | |||
| 3995 | (** Update the library to the given branch tip. Assumes that a |
||
| 3996 | local copy of the library already exists *) |
||
| 3997 | val update : context -> libname * source * branch -> unit result |
||
| 3998 | |||
| 3999 | (** Update the library to the given specific id or tag *) |
||
| 4000 | val update_to : context -> libname * source * id_or_tag -> unit result |
||
| 4001 | |||
| 4002 | (** Return a URL from which the library can be cloned, given that |
||
| 4003 | the local copy already exists. For a DVCS this can be the |
||
| 4004 | local copy, but for a centralised VCS it will have to be the |
||
| 4005 | remote repository URL. Used for archiving *) |
||
| 4006 | val copy_url_for : context -> libname -> string result |
||
| 4007 | end |
||
| 4008 | |||
| 4009 | signature LIB_CONTROL = sig |
||
| 4010 | val review : context -> libspec -> (libstate * localstate) result |
||
| 4011 | val status : context -> libspec -> (libstate * localstate) result |
||
| 4012 | val update : context -> libspec -> unit result |
||
| 4013 | val id_of : context -> libspec -> id_or_tag result |
||
| 4014 | 76:a6c9a0ca493e | Chris | val is_working : context -> vcs -> bool result |
| 4015 | 70:2d3e1d1f99c0 | Chris | end |
| 4016 | |||
| 4017 | structure FileBits :> sig |
||
| 4018 | val extpath : context -> string |
||
| 4019 | val libpath : context -> libname -> string |
||
| 4020 | val subpath : context -> libname -> string -> string |
||
| 4021 | val command_output : context -> libname -> string list -> string result |
||
| 4022 | val command : context -> libname -> string list -> unit result |
||
| 4023 | val file_url : string -> string |
||
| 4024 | val file_contents : string -> string |
||
| 4025 | val mydir : unit -> string |
||
| 4026 | val homedir : unit -> string |
||
| 4027 | val mkpath : string -> unit result |
||
| 4028 | val rmpath : string -> unit result |
||
| 4029 | val nonempty_dir_exists : string -> bool |
||
| 4030 | val project_spec_path : string -> string |
||
| 4031 | val project_lock_path : string -> string |
||
| 4032 | val verbose : unit -> bool |
||
| 4033 | end = struct |
||
| 4034 | |||
| 4035 | fun verbose () = |
||
| 4036 | 76:a6c9a0ca493e | Chris | case OS.Process.getEnv "REPOINT_VERBOSE" of |
| 4037 | 70:2d3e1d1f99c0 | Chris | SOME "0" => false |
| 4038 | | SOME _ => true |
||
| 4039 | | NONE => false |
||
| 4040 | |||
| 4041 | fun split_relative path desc = |
||
| 4042 | case OS.Path.fromString path of |
||
| 4043 | { isAbs = true, ... } => raise Fail (desc ^ " may not be absolute")
|
||
| 4044 | | { arcs, ... } => arcs
|
||
| 4045 | |||
| 4046 | fun extpath ({ rootpath, extdir, ... } : context) =
|
||
| 4047 | let val { isAbs, vol, arcs } = OS.Path.fromString rootpath
|
||
| 4048 | in OS.Path.toString {
|
||
| 4049 | isAbs = isAbs, |
||
| 4050 | vol = vol, |
||
| 4051 | arcs = arcs @ |
||
| 4052 | split_relative extdir "extdir" |
||
| 4053 | } |
||
| 4054 | end |
||
| 4055 | |||
| 4056 | fun subpath ({ rootpath, extdir, ... } : context) libname remainder =
|
||
| 4057 | (* NB libname is allowed to be a path fragment, e.g. foo/bar *) |
||
| 4058 | let val { isAbs, vol, arcs } = OS.Path.fromString rootpath
|
||
| 4059 | in OS.Path.toString {
|
||
| 4060 | isAbs = isAbs, |
||
| 4061 | vol = vol, |
||
| 4062 | arcs = arcs @ |
||
| 4063 | split_relative extdir "extdir" @ |
||
| 4064 | split_relative libname "library path" @ |
||
| 4065 | split_relative remainder "subpath" |
||
| 4066 | } |
||
| 4067 | end |
||
| 4068 | |||
| 4069 | fun libpath context "" = |
||
| 4070 | extpath context |
||
| 4071 | | libpath context libname = |
||
| 4072 | subpath context libname "" |
||
| 4073 | |||
| 4074 | fun project_file_path rootpath filename = |
||
| 4075 | let val { isAbs, vol, arcs } = OS.Path.fromString rootpath
|
||
| 4076 | in OS.Path.toString {
|
||
| 4077 | isAbs = isAbs, |
||
| 4078 | vol = vol, |
||
| 4079 | arcs = arcs @ [ filename ] |
||
| 4080 | } |
||
| 4081 | end |
||
| 4082 | |||
| 4083 | fun project_spec_path rootpath = |
||
| 4084 | 76:a6c9a0ca493e | Chris | project_file_path rootpath (RepointFilenames.project_file) |
| 4085 | 70:2d3e1d1f99c0 | Chris | |
| 4086 | fun project_lock_path rootpath = |
||
| 4087 | 76:a6c9a0ca493e | Chris | project_file_path rootpath (RepointFilenames.project_lock_file) |
| 4088 | 70:2d3e1d1f99c0 | Chris | |
| 4089 | fun trim str = |
||
| 4090 | hd (String.fields (fn x => x = #"\n" orelse x = #"\r") str) |
||
| 4091 | |||
| 4092 | fun file_url path = |
||
| 4093 | let val forward_path = |
||
| 4094 | String.translate (fn #"\\" => "/" | |
||
| 4095 | c => Char.toString c) |
||
| 4096 | (OS.Path.mkCanonical path) |
||
| 4097 | in |
||
| 4098 | (* Path is expected to be absolute already, but if it |
||
| 4099 | starts with a drive letter, we'll need an extra slash *) |
||
| 4100 | case explode forward_path of |
||
| 4101 | #"/"::rest => "file:///" ^ implode rest |
||
| 4102 | | _ => "file:///" ^ forward_path |
||
| 4103 | end |
||
| 4104 | |||
| 4105 | fun file_contents filename = |
||
| 4106 | let val stream = TextIO.openIn filename |
||
| 4107 | fun read_all str acc = |
||
| 4108 | case TextIO.inputLine str of |
||
| 4109 | SOME line => read_all str (trim line :: acc) |
||
| 4110 | | NONE => rev acc |
||
| 4111 | val contents = read_all stream [] |
||
| 4112 | val _ = TextIO.closeIn stream |
||
| 4113 | in |
||
| 4114 | String.concatWith "\n" contents |
||
| 4115 | end |
||
| 4116 | |||
| 4117 | fun expand_commandline cmdlist = |
||
| 4118 | 76:a6c9a0ca493e | Chris | (* We are quite strict about what we accept here, except |
| 4119 | 70:2d3e1d1f99c0 | Chris | for the first element in cmdlist which is assumed to be a |
| 4120 | 76:a6c9a0ca493e | Chris | known command location rather than arbitrary user input. *) |
| 4121 | 70:2d3e1d1f99c0 | Chris | let open Char |
| 4122 | fun quote arg = |
||
| 4123 | if List.all |
||
| 4124 | (fn c => isAlphaNum c orelse c = #"-" orelse c = #"_") |
||
| 4125 | (explode arg) |
||
| 4126 | then arg |
||
| 4127 | else "\"" ^ arg ^ "\"" |
||
| 4128 | fun check arg = |
||
| 4129 | let val valid = explode " /#:;?,._-{}@=+"
|
||
| 4130 | in |
||
| 4131 | app (fn c => |
||
| 4132 | if isAlphaNum c orelse |
||
| 4133 | List.exists (fn v => v = c) valid orelse |
||
| 4134 | c > chr 127 |
||
| 4135 | then () |
||
| 4136 | else raise Fail ("Invalid character '" ^
|
||
| 4137 | (Char.toString c) ^ |
||
| 4138 | "' in command list")) |
||
| 4139 | (explode arg); |
||
| 4140 | arg |
||
| 4141 | end |
||
| 4142 | in |
||
| 4143 | String.concatWith " " |
||
| 4144 | (map quote |
||
| 4145 | (hd cmdlist :: map check (tl cmdlist))) |
||
| 4146 | end |
||
| 4147 | |||
| 4148 | val tick_cycle = ref 0 |
||
| 4149 | val tick_chars = Vector.fromList (map String.str (explode "|/-\\")) |
||
| 4150 | |||
| 4151 | fun tick libname cmdlist = |
||
| 4152 | let val n = Vector.length tick_chars |
||
| 4153 | fun pad_to n str = |
||
| 4154 | if n <= String.size str then str |
||
| 4155 | else pad_to n (str ^ " ") |
||
| 4156 | val name = if libname <> "" then libname |
||
| 4157 | else if cmdlist = nil then "" |
||
| 4158 | else hd (rev cmdlist) |
||
| 4159 | in |
||
| 4160 | print (" " ^
|
||
| 4161 | Vector.sub(tick_chars, !tick_cycle) ^ " " ^ |
||
| 4162 | pad_to 70 name ^ |
||
| 4163 | "\r"); |
||
| 4164 | tick_cycle := (if !tick_cycle = n - 1 then 0 else 1 + !tick_cycle) |
||
| 4165 | end |
||
| 4166 | |||
| 4167 | fun run_command context libname cmdlist redirect = |
||
| 4168 | let open OS |
||
| 4169 | val dir = libpath context libname |
||
| 4170 | val cmd = expand_commandline cmdlist |
||
| 4171 | val _ = if verbose () |
||
| 4172 | then print ("\n=== " ^ dir ^ "\n<<< " ^ cmd ^ "\n")
|
||
| 4173 | else tick libname cmdlist |
||
| 4174 | val _ = FileSys.chDir dir |
||
| 4175 | val status = case redirect of |
||
| 4176 | NONE => Process.system cmd |
||
| 4177 | | SOME file => Process.system (cmd ^ ">" ^ file) |
||
| 4178 | in |
||
| 4179 | if Process.isSuccess status |
||
| 4180 | then OK () |
||
| 4181 | else ERROR ("Command failed: " ^ cmd ^ " (in dir " ^ dir ^ ")")
|
||
| 4182 | end |
||
| 4183 | handle ex => ERROR ("Unable to run command: " ^ exnMessage ex)
|
||
| 4184 | |||
| 4185 | fun command context libname cmdlist = |
||
| 4186 | run_command context libname cmdlist NONE |
||
| 4187 | |||
| 4188 | fun command_output context libname cmdlist = |
||
| 4189 | let open OS |
||
| 4190 | val tmpFile = FileSys.tmpName () |
||
| 4191 | val result = run_command context libname cmdlist (SOME tmpFile) |
||
| 4192 | val contents = file_contents tmpFile |
||
| 4193 | val _ = if verbose () |
||
| 4194 | then print (">>> \"" ^ contents ^ "\"\n")
|
||
| 4195 | else () |
||
| 4196 | in |
||
| 4197 | FileSys.remove tmpFile handle _ => (); |
||
| 4198 | case result of |
||
| 4199 | OK () => OK contents |
||
| 4200 | | ERROR e => ERROR e |
||
| 4201 | end |
||
| 4202 | |||
| 4203 | fun mydir () = |
||
| 4204 | let open OS |
||
| 4205 | val { dir, file } = Path.splitDirFile (CommandLine.name ())
|
||
| 4206 | in |
||
| 4207 | FileSys.realPath |
||
| 4208 | (if Path.isAbsolute dir |
||
| 4209 | then dir |
||
| 4210 | else Path.concat (FileSys.getDir (), dir)) |
||
| 4211 | end |
||
| 4212 | |||
| 4213 | fun homedir () = |
||
| 4214 | (* Failure is not routine, so we use an exception here *) |
||
| 4215 | case (OS.Process.getEnv "HOME", |
||
| 4216 | OS.Process.getEnv "HOMEPATH") of |
||
| 4217 | (SOME home, _) => home |
||
| 4218 | | (NONE, SOME home) => home |
||
| 4219 | | (NONE, NONE) => |
||
| 4220 | raise Fail "Failed to look up home directory from environment" |
||
| 4221 | |||
| 4222 | fun mkpath' path = |
||
| 4223 | if OS.FileSys.isDir path handle _ => false |
||
| 4224 | then OK () |
||
| 4225 | else case OS.Path.fromString path of |
||
| 4226 | { arcs = nil, ... } => OK ()
|
||
| 4227 | | { isAbs = false, ... } => ERROR "mkpath requires absolute path"
|
||
| 4228 | | { isAbs, vol, arcs } =>
|
||
| 4229 | case mkpath' (OS.Path.toString { (* parent *)
|
||
| 4230 | isAbs = isAbs, |
||
| 4231 | vol = vol, |
||
| 4232 | arcs = rev (tl (rev arcs)) }) of |
||
| 4233 | ERROR e => ERROR e |
||
| 4234 | | OK () => ((OS.FileSys.mkDir path; OK ()) |
||
| 4235 | handle OS.SysErr (e, _) => |
||
| 4236 | ERROR ("Directory creation failed: " ^ e))
|
||
| 4237 | |||
| 4238 | fun mkpath path = |
||
| 4239 | mkpath' (OS.Path.mkCanonical path) |
||
| 4240 | |||
| 4241 | fun dir_contents dir = |
||
| 4242 | let open OS |
||
| 4243 | fun files_from dirstream = |
||
| 4244 | case FileSys.readDir dirstream of |
||
| 4245 | NONE => [] |
||
| 4246 | | SOME file => |
||
| 4247 | (* readDir is supposed to filter these, |
||
| 4248 | but let's be extra cautious: *) |
||
| 4249 | if file = Path.parentArc orelse file = Path.currentArc |
||
| 4250 | then files_from dirstream |
||
| 4251 | else file :: files_from dirstream |
||
| 4252 | val stream = FileSys.openDir dir |
||
| 4253 | val files = map (fn f => Path.joinDirFile |
||
| 4254 | { dir = dir, file = f })
|
||
| 4255 | (files_from stream) |
||
| 4256 | val _ = FileSys.closeDir stream |
||
| 4257 | in |
||
| 4258 | files |
||
| 4259 | end |
||
| 4260 | |||
| 4261 | fun rmpath' path = |
||
| 4262 | let open OS |
||
| 4263 | fun remove path = |
||
| 4264 | if FileSys.isLink path (* dangling links bother isDir *) |
||
| 4265 | then FileSys.remove path |
||
| 4266 | else if FileSys.isDir path |
||
| 4267 | then (app remove (dir_contents path); FileSys.rmDir path) |
||
| 4268 | else FileSys.remove path |
||
| 4269 | in |
||
| 4270 | (remove path; OK ()) |
||
| 4271 | handle SysErr (e, _) => ERROR ("Path removal failed: " ^ e)
|
||
| 4272 | end |
||
| 4273 | |||
| 4274 | fun rmpath path = |
||
| 4275 | rmpath' (OS.Path.mkCanonical path) |
||
| 4276 | |||
| 4277 | fun nonempty_dir_exists path = |
||
| 4278 | let open OS.FileSys |
||
| 4279 | in |
||
| 4280 | (not (isLink path) andalso |
||
| 4281 | isDir path andalso |
||
| 4282 | dir_contents path <> []) |
||
| 4283 | handle _ => false |
||
| 4284 | end |
||
| 4285 | |||
| 4286 | end |
||
| 4287 | |||
| 4288 | functor LibControlFn (V: VCS_CONTROL) :> LIB_CONTROL = struct |
||
| 4289 | |||
| 4290 | (* Valid states for unpinned libraries: |
||
| 4291 | |||
| 4292 | - CORRECT: We are on the right branch and are up-to-date with |
||
| 4293 | it as far as we can tell. (If not using the network, this |
||
| 4294 | should be reported to user as "Present" rather than "Correct" |
||
| 4295 | as the remote repo may have advanced without us knowing.) |
||
| 4296 | |||
| 4297 | - SUPERSEDED: We are on the right branch but we can see that |
||
| 4298 | there is a newer revision either locally or on the remote (in |
||
| 4299 | Git terms, we are at an ancestor of the desired branch tip). |
||
| 4300 | |||
| 4301 | - WRONG: We are on the wrong branch (in Git terms, we are not |
||
| 4302 | at the desired branch tip or any ancestor of it). |
||
| 4303 | |||
| 4304 | - ABSENT: Repo doesn't exist here at all. |
||
| 4305 | |||
| 4306 | Valid states for pinned libraries: |
||
| 4307 | |||
| 4308 | - CORRECT: We are at the pinned revision. |
||
| 4309 | |||
| 4310 | - WRONG: We are at any revision other than the pinned one. |
||
| 4311 | |||
| 4312 | - ABSENT: Repo doesn't exist here at all. |
||
| 4313 | *) |
||
| 4314 | |||
| 4315 | fun check with_network context |
||
| 4316 | ({ libname, source, branch,
|
||
| 4317 | project_pin, lock_pin, ... } : libspec) = |
||
| 4318 | let fun check_unpinned () = |
||
| 4319 | let val newest = |
||
| 4320 | if with_network |
||
| 4321 | then V.is_newest context (libname, source, branch) |
||
| 4322 | else V.is_newest_locally context (libname, branch) |
||
| 4323 | in |
||
| 4324 | case newest of |
||
| 4325 | ERROR e => ERROR e |
||
| 4326 | | OK true => OK CORRECT |
||
| 4327 | | OK false => |
||
| 4328 | case V.is_on_branch context (libname, branch) of |
||
| 4329 | ERROR e => ERROR e |
||
| 4330 | | OK true => OK SUPERSEDED |
||
| 4331 | | OK false => OK WRONG |
||
| 4332 | end |
||
| 4333 | fun check_pinned target = |
||
| 4334 | case V.is_at context (libname, target) of |
||
| 4335 | ERROR e => ERROR e |
||
| 4336 | | OK true => OK CORRECT |
||
| 4337 | | OK false => OK WRONG |
||
| 4338 | fun check_remote () = |
||
| 4339 | case project_pin of |
||
| 4340 | UNPINNED => check_unpinned () |
||
| 4341 | | PINNED target => check_pinned target |
||
| 4342 | fun check_local () = |
||
| 4343 | case V.is_modified_locally context libname of |
||
| 4344 | ERROR e => ERROR e |
||
| 4345 | | OK true => OK MODIFIED |
||
| 4346 | | OK false => |
||
| 4347 | case lock_pin of |
||
| 4348 | UNPINNED => OK CLEAN |
||
| 4349 | | PINNED target => |
||
| 4350 | case V.is_at context (libname, target) of |
||
| 4351 | ERROR e => ERROR e |
||
| 4352 | | OK true => OK CLEAN |
||
| 4353 | | OK false => OK LOCK_MISMATCHED |
||
| 4354 | in |
||
| 4355 | case V.exists context libname of |
||
| 4356 | ERROR e => ERROR e |
||
| 4357 | | OK false => OK (ABSENT, CLEAN) |
||
| 4358 | | OK true => |
||
| 4359 | case (check_remote (), check_local ()) of |
||
| 4360 | (ERROR e, _) => ERROR e |
||
| 4361 | | (_, ERROR e) => ERROR e |
||
| 4362 | | (OK r, OK l) => OK (r, l) |
||
| 4363 | end |
||
| 4364 | |||
| 4365 | val review = check true |
||
| 4366 | val status = check false |
||
| 4367 | |||
| 4368 | fun update context |
||
| 4369 | ({ libname, source, branch,
|
||
| 4370 | project_pin, lock_pin, ... } : libspec) = |
||
| 4371 | let fun update_unpinned () = |
||
| 4372 | case V.is_newest context (libname, source, branch) of |
||
| 4373 | ERROR e => ERROR e |
||
| 4374 | | OK true => OK () |
||
| 4375 | | OK false => V.update context (libname, source, branch) |
||
| 4376 | fun update_pinned target = |
||
| 4377 | case V.is_at context (libname, target) of |
||
| 4378 | ERROR e => ERROR e |
||
| 4379 | | OK true => OK () |
||
| 4380 | | OK false => V.update_to context (libname, source, target) |
||
| 4381 | fun update' () = |
||
| 4382 | case lock_pin of |
||
| 4383 | PINNED target => update_pinned target |
||
| 4384 | | UNPINNED => |
||
| 4385 | case project_pin of |
||
| 4386 | PINNED target => update_pinned target |
||
| 4387 | | UNPINNED => update_unpinned () |
||
| 4388 | in |
||
| 4389 | case V.exists context libname of |
||
| 4390 | ERROR e => ERROR e |
||
| 4391 | | OK true => update' () |
||
| 4392 | | OK false => |
||
| 4393 | case V.checkout context (libname, source, branch) of |
||
| 4394 | ERROR e => ERROR e |
||
| 4395 | | OK () => update' () |
||
| 4396 | end |
||
| 4397 | |||
| 4398 | fun id_of context ({ libname, ... } : libspec) =
|
||
| 4399 | V.id_of context libname |
||
| 4400 | 76:a6c9a0ca493e | Chris | |
| 4401 | fun is_working context vcs = |
||
| 4402 | V.is_working context |
||
| 4403 | 70:2d3e1d1f99c0 | Chris | |
| 4404 | end |
||
| 4405 | |||
| 4406 | (* Simple Standard ML JSON parser |
||
| 4407 | https://bitbucket.org/cannam/sml-simplejson |
||
| 4408 | Copyright 2017 Chris Cannam. BSD licence. |
||
| 4409 | Parts based on the JSON parser in the Ponyo library by Phil Eaton. |
||
| 4410 | *) |
||
| 4411 | |||
| 4412 | signature JSON = sig |
||
| 4413 | |||
| 4414 | datatype json = OBJECT of (string * json) list |
||
| 4415 | | ARRAY of json list |
||
| 4416 | | NUMBER of real |
||
| 4417 | | STRING of string |
||
| 4418 | | BOOL of bool |
||
| 4419 | | NULL |
||
| 4420 | |||
| 4421 | datatype 'a result = OK of 'a |
||
| 4422 | | ERROR of string |
||
| 4423 | |||
| 4424 | val parse : string -> json result |
||
| 4425 | val serialise : json -> string |
||
| 4426 | val serialiseIndented : json -> string |
||
| 4427 | |||
| 4428 | end |
||
| 4429 | |||
| 4430 | structure Json :> JSON = struct |
||
| 4431 | |||
| 4432 | datatype json = OBJECT of (string * json) list |
||
| 4433 | | ARRAY of json list |
||
| 4434 | | NUMBER of real |
||
| 4435 | | STRING of string |
||
| 4436 | | BOOL of bool |
||
| 4437 | | NULL |
||
| 4438 | |||
| 4439 | datatype 'a result = OK of 'a |
||
| 4440 | | ERROR of string |
||
| 4441 | |||
| 4442 | structure T = struct |
||
| 4443 | datatype token = NUMBER of char list |
||
| 4444 | | STRING of string |
||
| 4445 | | BOOL of bool |
||
| 4446 | | NULL |
||
| 4447 | | CURLY_L |
||
| 4448 | | CURLY_R |
||
| 4449 | | SQUARE_L |
||
| 4450 | | SQUARE_R |
||
| 4451 | | COLON |
||
| 4452 | | COMMA |
||
| 4453 | |||
| 4454 | fun toString t = |
||
| 4455 | case t of NUMBER digits => implode digits |
||
| 4456 | | STRING s => s |
||
| 4457 | | BOOL b => Bool.toString b |
||
| 4458 | | NULL => "null" |
||
| 4459 | | CURLY_L => "{"
|
||
| 4460 | | CURLY_R => "}" |
||
| 4461 | | SQUARE_L => "[" |
||
| 4462 | | SQUARE_R => "]" |
||
| 4463 | | COLON => ":" |
||
| 4464 | | COMMA => "," |
||
| 4465 | end |
||
| 4466 | |||
| 4467 | fun bmpToUtf8 cp = (* convert a codepoint in Unicode BMP to utf8 bytes *) |
||
| 4468 | let open Word |
||
| 4469 | infix 6 orb andb >> |
||
| 4470 | in |
||
| 4471 | map (Char.chr o toInt) |
||
| 4472 | (if cp < 0wx80 then |
||
| 4473 | [cp] |
||
| 4474 | else if cp < 0wx800 then |
||
| 4475 | [0wxc0 orb (cp >> 0w6), 0wx80 orb (cp andb 0wx3f)] |
||
| 4476 | else if cp < 0wx10000 then |
||
| 4477 | [0wxe0 orb (cp >> 0w12), |
||
| 4478 | 0wx80 orb ((cp >> 0w6) andb 0wx3f), |
||
| 4479 | 0wx80 orb (cp andb 0wx3f)] |
||
| 4480 | else raise Fail ("Invalid BMP point " ^ (Word.toString cp)))
|
||
| 4481 | end |
||
| 4482 | |||
| 4483 | fun error pos text = ERROR (text ^ " at character position " ^ |
||
| 4484 | Int.toString (pos - 1)) |
||
| 4485 | fun token_error pos = error pos ("Unexpected token")
|
||
| 4486 | |||
| 4487 | fun lexNull pos acc (#"u" :: #"l" :: #"l" :: xs) = |
||
| 4488 | lex (pos + 3) (T.NULL :: acc) xs |
||
| 4489 | | lexNull pos acc _ = token_error pos |
||
| 4490 | |||
| 4491 | and lexTrue pos acc (#"r" :: #"u" :: #"e" :: xs) = |
||
| 4492 | lex (pos + 3) (T.BOOL true :: acc) xs |
||
| 4493 | | lexTrue pos acc _ = token_error pos |
||
| 4494 | |||
| 4495 | and lexFalse pos acc (#"a" :: #"l" :: #"s" :: #"e" :: xs) = |
||
| 4496 | lex (pos + 4) (T.BOOL false :: acc) xs |
||
| 4497 | | lexFalse pos acc _ = token_error pos |
||
| 4498 | |||
| 4499 | and lexChar tok pos acc xs = |
||
| 4500 | lex pos (tok :: acc) xs |
||
| 4501 | |||
| 4502 | and lexString pos acc cc = |
||
| 4503 | let datatype escaped = ESCAPED | NORMAL |
||
| 4504 | fun lexString' pos text ESCAPED [] = |
||
| 4505 | error pos "End of input during escape sequence" |
||
| 4506 | | lexString' pos text NORMAL [] = |
||
| 4507 | error pos "End of input during string" |
||
| 4508 | | lexString' pos text ESCAPED (x :: xs) = |
||
| 4509 | let fun esc c = lexString' (pos + 1) (c :: text) NORMAL xs |
||
| 4510 | in case x of |
||
| 4511 | #"\"" => esc x |
||
| 4512 | | #"\\" => esc x |
||
| 4513 | | #"/" => esc x |
||
| 4514 | | #"b" => esc #"\b" |
||
| 4515 | | #"f" => esc #"\f" |
||
| 4516 | | #"n" => esc #"\n" |
||
| 4517 | | #"r" => esc #"\r" |
||
| 4518 | | #"t" => esc #"\t" |
||
| 4519 | | _ => error pos ("Invalid escape \\" ^
|
||
| 4520 | Char.toString x) |
||
| 4521 | end |
||
| 4522 | | lexString' pos text NORMAL (#"\\" :: #"u" ::a::b::c::d:: xs) = |
||
| 4523 | if List.all Char.isHexDigit [a,b,c,d] |
||
| 4524 | then case Word.fromString ("0wx" ^ (implode [a,b,c,d])) of
|
||
| 4525 | SOME w => (let val utf = rev (bmpToUtf8 w) in |
||
| 4526 | lexString' (pos + 6) (utf @ text) |
||
| 4527 | NORMAL xs |
||
| 4528 | end |
||
| 4529 | handle Fail err => error pos err) |
||
| 4530 | | NONE => error pos "Invalid Unicode BMP escape sequence" |
||
| 4531 | else error pos "Invalid Unicode BMP escape sequence" |
||
| 4532 | | lexString' pos text NORMAL (x :: xs) = |
||
| 4533 | if Char.ord x < 0x20 |
||
| 4534 | then error pos "Invalid unescaped control character" |
||
| 4535 | else |
||
| 4536 | case x of |
||
| 4537 | #"\"" => OK (rev text, xs, pos + 1) |
||
| 4538 | | #"\\" => lexString' (pos + 1) text ESCAPED xs |
||
| 4539 | | _ => lexString' (pos + 1) (x :: text) NORMAL xs |
||
| 4540 | in |
||
| 4541 | case lexString' pos [] NORMAL cc of |
||
| 4542 | OK (text, rest, newpos) => |
||
| 4543 | lex newpos (T.STRING (implode text) :: acc) rest |
||
| 4544 | | ERROR e => ERROR e |
||
| 4545 | end |
||
| 4546 | |||
| 4547 | and lexNumber firstChar pos acc cc = |
||
| 4548 | let val valid = explode ".+-e" |
||
| 4549 | fun lexNumber' pos digits [] = (rev digits, [], pos) |
||
| 4550 | | lexNumber' pos digits (x :: xs) = |
||
| 4551 | if x = #"E" then lexNumber' (pos + 1) (#"e" :: digits) xs |
||
| 4552 | else if Char.isDigit x orelse List.exists (fn c => x = c) valid |
||
| 4553 | then lexNumber' (pos + 1) (x :: digits) xs |
||
| 4554 | else (rev digits, x :: xs, pos) |
||
| 4555 | val (digits, rest, newpos) = |
||
| 4556 | lexNumber' (pos - 1) [] (firstChar :: cc) |
||
| 4557 | in |
||
| 4558 | case digits of |
||
| 4559 | [] => token_error pos |
||
| 4560 | | _ => lex newpos (T.NUMBER digits :: acc) rest |
||
| 4561 | end |
||
| 4562 | |||
| 4563 | and lex pos acc [] = OK (rev acc) |
||
| 4564 | | lex pos acc (x::xs) = |
||
| 4565 | (case x of |
||
| 4566 | #" " => lex |
||
| 4567 | | #"\t" => lex |
||
| 4568 | | #"\n" => lex |
||
| 4569 | | #"\r" => lex |
||
| 4570 | | #"{" => lexChar T.CURLY_L
|
||
| 4571 | | #"}" => lexChar T.CURLY_R |
||
| 4572 | | #"[" => lexChar T.SQUARE_L |
||
| 4573 | | #"]" => lexChar T.SQUARE_R |
||
| 4574 | | #":" => lexChar T.COLON |
||
| 4575 | | #"," => lexChar T.COMMA |
||
| 4576 | | #"\"" => lexString |
||
| 4577 | | #"t" => lexTrue |
||
| 4578 | | #"f" => lexFalse |
||
| 4579 | | #"n" => lexNull |
||
| 4580 | | x => lexNumber x) (pos + 1) acc xs |
||
| 4581 | |||
| 4582 | fun show [] = "end of input" |
||
| 4583 | | show (tok :: _) = T.toString tok |
||
| 4584 | |||
| 4585 | fun parseNumber digits = |
||
| 4586 | (* Note lexNumber already case-insensitised the E for us *) |
||
| 4587 | let open Char |
||
| 4588 | |||
| 4589 | fun okExpDigits [] = false |
||
| 4590 | | okExpDigits (c :: []) = isDigit c |
||
| 4591 | | okExpDigits (c :: cs) = isDigit c andalso okExpDigits cs |
||
| 4592 | |||
| 4593 | fun okExponent [] = false |
||
| 4594 | | okExponent (#"+" :: cs) = okExpDigits cs |
||
| 4595 | | okExponent (#"-" :: cs) = okExpDigits cs |
||
| 4596 | | okExponent cc = okExpDigits cc |
||
| 4597 | |||
| 4598 | fun okFracTrailing [] = true |
||
| 4599 | | okFracTrailing (c :: cs) = |
||
| 4600 | (isDigit c andalso okFracTrailing cs) orelse |
||
| 4601 | (c = #"e" andalso okExponent cs) |
||
| 4602 | |||
| 4603 | fun okFraction [] = false |
||
| 4604 | | okFraction (c :: cs) = |
||
| 4605 | isDigit c andalso okFracTrailing cs |
||
| 4606 | |||
| 4607 | fun okPosTrailing [] = true |
||
| 4608 | | okPosTrailing (#"." :: cs) = okFraction cs |
||
| 4609 | | okPosTrailing (#"e" :: cs) = okExponent cs |
||
| 4610 | | okPosTrailing (c :: cs) = |
||
| 4611 | isDigit c andalso okPosTrailing cs |
||
| 4612 | |||
| 4613 | fun okPositive [] = false |
||
| 4614 | | okPositive (#"0" :: []) = true |
||
| 4615 | | okPositive (#"0" :: #"." :: cs) = okFraction cs |
||
| 4616 | | okPositive (#"0" :: #"e" :: cs) = okExponent cs |
||
| 4617 | | okPositive (#"0" :: cs) = false |
||
| 4618 | | okPositive (c :: cs) = isDigit c andalso okPosTrailing cs |
||
| 4619 | |||
| 4620 | fun okNumber (#"-" :: cs) = okPositive cs |
||
| 4621 | | okNumber cc = okPositive cc |
||
| 4622 | in |
||
| 4623 | if okNumber digits |
||
| 4624 | then case Real.fromString (implode digits) of |
||
| 4625 | NONE => ERROR "Number out of range" |
||
| 4626 | | SOME r => OK r |
||
| 4627 | else ERROR ("Invalid number \"" ^ (implode digits) ^ "\"")
|
||
| 4628 | end |
||
| 4629 | |||
| 4630 | fun parseObject (T.CURLY_R :: xs) = OK (OBJECT [], xs) |
||
| 4631 | | parseObject tokens = |
||
| 4632 | let fun parsePair (T.STRING key :: T.COLON :: xs) = |
||
| 4633 | (case parseTokens xs of |
||
| 4634 | ERROR e => ERROR e |
||
| 4635 | | OK (j, xs) => OK ((key, j), xs)) |
||
| 4636 | | parsePair other = |
||
| 4637 | ERROR ("Object key/value pair expected around \"" ^
|
||
| 4638 | show other ^ "\"") |
||
| 4639 | fun parseObject' acc [] = ERROR "End of input during object" |
||
| 4640 | | parseObject' acc tokens = |
||
| 4641 | case parsePair tokens of |
||
| 4642 | ERROR e => ERROR e |
||
| 4643 | | OK (pair, T.COMMA :: xs) => |
||
| 4644 | parseObject' (pair :: acc) xs |
||
| 4645 | | OK (pair, T.CURLY_R :: xs) => |
||
| 4646 | OK (OBJECT (rev (pair :: acc)), xs) |
||
| 4647 | | OK (_, _) => ERROR "Expected , or } after object element" |
||
| 4648 | in |
||
| 4649 | parseObject' [] tokens |
||
| 4650 | end |
||
| 4651 | |||
| 4652 | and parseArray (T.SQUARE_R :: xs) = OK (ARRAY [], xs) |
||
| 4653 | | parseArray tokens = |
||
| 4654 | let fun parseArray' acc [] = ERROR "End of input during array" |
||
| 4655 | | parseArray' acc tokens = |
||
| 4656 | case parseTokens tokens of |
||
| 4657 | ERROR e => ERROR e |
||
| 4658 | | OK (j, T.COMMA :: xs) => parseArray' (j :: acc) xs |
||
| 4659 | | OK (j, T.SQUARE_R :: xs) => OK (ARRAY (rev (j :: acc)), xs) |
||
| 4660 | | OK (_, _) => ERROR "Expected , or ] after array element" |
||
| 4661 | in |
||
| 4662 | parseArray' [] tokens |
||
| 4663 | end |
||
| 4664 | |||
| 4665 | and parseTokens [] = ERROR "Value expected" |
||
| 4666 | | parseTokens (tok :: xs) = |
||
| 4667 | (case tok of |
||
| 4668 | T.NUMBER d => (case parseNumber d of |
||
| 4669 | OK r => OK (NUMBER r, xs) |
||
| 4670 | | ERROR e => ERROR e) |
||
| 4671 | | T.STRING s => OK (STRING s, xs) |
||
| 4672 | | T.BOOL b => OK (BOOL b, xs) |
||
| 4673 | | T.NULL => OK (NULL, xs) |
||
| 4674 | | T.CURLY_L => parseObject xs |
||
| 4675 | | T.SQUARE_L => parseArray xs |
||
| 4676 | | _ => ERROR ("Unexpected token " ^ T.toString tok ^
|
||
| 4677 | " before " ^ show xs)) |
||
| 4678 | |||
| 4679 | fun parse str = |
||
| 4680 | case lex 1 [] (explode str) of |
||
| 4681 | ERROR e => ERROR e |
||
| 4682 | | OK tokens => case parseTokens tokens of |
||
| 4683 | OK (value, []) => OK value |
||
| 4684 | | OK (_, _) => ERROR "Extra data after input" |
||
| 4685 | | ERROR e => ERROR e |
||
| 4686 | |||
| 4687 | fun stringEscape s = |
||
| 4688 | let fun esc x = [x, #"\\"] |
||
| 4689 | fun escape' acc [] = rev acc |
||
| 4690 | | escape' acc (x :: xs) = |
||
| 4691 | escape' (case x of |
||
| 4692 | #"\"" => esc x @ acc |
||
| 4693 | | #"\\" => esc x @ acc |
||
| 4694 | | #"\b" => esc #"b" @ acc |
||
| 4695 | | #"\f" => esc #"f" @ acc |
||
| 4696 | | #"\n" => esc #"n" @ acc |
||
| 4697 | | #"\r" => esc #"r" @ acc |
||
| 4698 | | #"\t" => esc #"t" @ acc |
||
| 4699 | | _ => |
||
| 4700 | let val c = Char.ord x |
||
| 4701 | in |
||
| 4702 | if c < 0x20 |
||
| 4703 | then let val hex = Word.toString (Word.fromInt c) |
||
| 4704 | in (rev o explode) (if c < 0x10 |
||
| 4705 | then ("\\u000" ^ hex)
|
||
| 4706 | else ("\\u00" ^ hex))
|
||
| 4707 | end @ acc |
||
| 4708 | else |
||
| 4709 | x :: acc |
||
| 4710 | end) |
||
| 4711 | xs |
||
| 4712 | in |
||
| 4713 | implode (escape' [] (explode s)) |
||
| 4714 | end |
||
| 4715 | |||
| 4716 | fun serialise json = |
||
| 4717 | case json of |
||
| 4718 | OBJECT pp => "{" ^ String.concatWith
|
||
| 4719 | "," (map (fn (key, value) => |
||
| 4720 | serialise (STRING key) ^ ":" ^ |
||
| 4721 | serialise value) pp) ^ |
||
| 4722 | "}" |
||
| 4723 | | ARRAY arr => "[" ^ String.concatWith "," (map serialise arr) ^ "]" |
||
| 4724 | | NUMBER n => implode (map (fn #"~" => #"-" | c => c) |
||
| 4725 | (explode (Real.toString n))) |
||
| 4726 | | STRING s => "\"" ^ stringEscape s ^ "\"" |
||
| 4727 | | BOOL b => Bool.toString b |
||
| 4728 | | NULL => "null" |
||
| 4729 | |||
| 4730 | fun serialiseIndented json = |
||
| 4731 | let fun indent 0 = "" |
||
| 4732 | | indent i = " " ^ indent (i - 1) |
||
| 4733 | fun serialiseIndented' i json = |
||
| 4734 | let val ser = serialiseIndented' (i + 1) |
||
| 4735 | in |
||
| 4736 | case json of |
||
| 4737 | OBJECT [] => "{}"
|
||
| 4738 | | ARRAY [] => "[]" |
||
| 4739 | | OBJECT pp => "{\n" ^ indent (i + 1) ^
|
||
| 4740 | String.concatWith |
||
| 4741 | (",\n" ^ indent (i + 1))
|
||
| 4742 | (map (fn (key, value) => |
||
| 4743 | ser (STRING key) ^ ": " ^ |
||
| 4744 | ser value) pp) ^ |
||
| 4745 | "\n" ^ indent i ^ "}" |
||
| 4746 | | ARRAY arr => "[\n" ^ indent (i + 1) ^ |
||
| 4747 | String.concatWith |
||
| 4748 | (",\n" ^ indent (i + 1))
|
||
| 4749 | (map ser arr) ^ |
||
| 4750 | "\n" ^ indent i ^ "]" |
||
| 4751 | | other => serialise other |
||
| 4752 | end |
||
| 4753 | in |
||
| 4754 | serialiseIndented' 0 json ^ "\n" |
||
| 4755 | end |
||
| 4756 | |||
| 4757 | end |
||
| 4758 | |||
| 4759 | |||
| 4760 | structure JsonBits :> sig |
||
| 4761 | 76:a6c9a0ca493e | Chris | exception Config of string |
| 4762 | 70:2d3e1d1f99c0 | Chris | val load_json_from : string -> Json.json (* filename -> json *) |
| 4763 | val save_json_to : string -> Json.json -> unit |
||
| 4764 | val lookup_optional : Json.json -> string list -> Json.json option |
||
| 4765 | val lookup_optional_string : Json.json -> string list -> string option |
||
| 4766 | val lookup_mandatory : Json.json -> string list -> Json.json |
||
| 4767 | val lookup_mandatory_string : Json.json -> string list -> string |
||
| 4768 | end = struct |
||
| 4769 | |||
| 4770 | 76:a6c9a0ca493e | Chris | exception Config of string |
| 4771 | |||
| 4772 | 70:2d3e1d1f99c0 | Chris | fun load_json_from filename = |
| 4773 | case Json.parse (FileBits.file_contents filename) of |
||
| 4774 | Json.OK json => json |
||
| 4775 | 76:a6c9a0ca493e | Chris | | Json.ERROR e => raise Config ("Failed to parse file: " ^ e)
|
| 4776 | 70:2d3e1d1f99c0 | Chris | |
| 4777 | fun save_json_to filename json = |
||
| 4778 | (* using binary I/O to avoid ever writing CR/LF line endings *) |
||
| 4779 | let val jstr = Json.serialiseIndented json |
||
| 4780 | val stream = BinIO.openOut filename |
||
| 4781 | in |
||
| 4782 | BinIO.output (stream, Byte.stringToBytes jstr); |
||
| 4783 | BinIO.closeOut stream |
||
| 4784 | end |
||
| 4785 | |||
| 4786 | fun lookup_optional json kk = |
||
| 4787 | let fun lookup key = |
||
| 4788 | case json of |
||
| 4789 | Json.OBJECT kvs => |
||
| 4790 | 76:a6c9a0ca493e | Chris | (case List.filter (fn (k, v) => k = key) kvs of |
| 4791 | [] => NONE |
||
| 4792 | | [(_,v)] => SOME v |
||
| 4793 | | _ => raise Config ("Duplicate key: " ^
|
||
| 4794 | (String.concatWith " -> " kk))) |
||
| 4795 | | _ => raise Config "Object expected" |
||
| 4796 | 70:2d3e1d1f99c0 | Chris | in |
| 4797 | case kk of |
||
| 4798 | [] => NONE |
||
| 4799 | | key::[] => lookup key |
||
| 4800 | | key::kk => case lookup key of |
||
| 4801 | NONE => NONE |
||
| 4802 | | SOME j => lookup_optional j kk |
||
| 4803 | end |
||
| 4804 | |||
| 4805 | fun lookup_optional_string json kk = |
||
| 4806 | case lookup_optional json kk of |
||
| 4807 | SOME (Json.STRING s) => SOME s |
||
| 4808 | 76:a6c9a0ca493e | Chris | | SOME _ => raise Config ("Value (if present) must be string: " ^
|
| 4809 | (String.concatWith " -> " kk)) |
||
| 4810 | 70:2d3e1d1f99c0 | Chris | | NONE => NONE |
| 4811 | |||
| 4812 | fun lookup_mandatory json kk = |
||
| 4813 | case lookup_optional json kk of |
||
| 4814 | SOME v => v |
||
| 4815 | 76:a6c9a0ca493e | Chris | | NONE => raise Config ("Value is mandatory: " ^
|
| 4816 | (String.concatWith " -> " kk)) |
||
| 4817 | 70:2d3e1d1f99c0 | Chris | |
| 4818 | fun lookup_mandatory_string json kk = |
||
| 4819 | case lookup_optional json kk of |
||
| 4820 | SOME (Json.STRING s) => s |
||
| 4821 | 76:a6c9a0ca493e | Chris | | _ => raise Config ("Value must be string: " ^
|
| 4822 | (String.concatWith " -> " kk)) |
||
| 4823 | 70:2d3e1d1f99c0 | Chris | end |
| 4824 | |||
| 4825 | structure Provider :> sig |
||
| 4826 | val load_providers : Json.json -> provider list |
||
| 4827 | val load_more_providers : provider list -> Json.json -> provider list |
||
| 4828 | val remote_url : context -> vcs -> source -> libname -> string |
||
| 4829 | end = struct |
||
| 4830 | |||
| 4831 | val known_providers : provider list = |
||
| 4832 | [ {
|
||
| 4833 | service = "bitbucket", |
||
| 4834 | supports = [HG, GIT], |
||
| 4835 | remote_spec = {
|
||
| 4836 | anon = SOME "https://bitbucket.org/{owner}/{repository}",
|
||
| 4837 | auth = SOME "ssh://{vcs}@bitbucket.org/{owner}/{repository}"
|
||
| 4838 | } |
||
| 4839 | }, |
||
| 4840 | {
|
||
| 4841 | service = "github", |
||
| 4842 | supports = [GIT], |
||
| 4843 | remote_spec = {
|
||
| 4844 | anon = SOME "https://github.com/{owner}/{repository}",
|
||
| 4845 | auth = SOME "ssh://{vcs}@github.com/{owner}/{repository}"
|
||
| 4846 | } |
||
| 4847 | } |
||
| 4848 | ] |
||
| 4849 | |||
| 4850 | fun vcs_name vcs = |
||
| 4851 | case vcs of HG => "hg" |
||
| 4852 | | GIT => "git" |
||
| 4853 | | SVN => "svn" |
||
| 4854 | |||
| 4855 | fun vcs_from_name name = |
||
| 4856 | case name of "hg" => HG |
||
| 4857 | | "git" => GIT |
||
| 4858 | | "svn" => SVN |
||
| 4859 | | other => raise Fail ("Unknown vcs name \"" ^ name ^ "\"")
|
||
| 4860 | |||
| 4861 | fun load_more_providers previously_loaded json = |
||
| 4862 | let open JsonBits |
||
| 4863 | fun load pjson pname : provider = |
||
| 4864 | {
|
||
| 4865 | service = pname, |
||
| 4866 | supports = |
||
| 4867 | case lookup_mandatory pjson ["vcs"] of |
||
| 4868 | Json.ARRAY vv => |
||
| 4869 | map (fn (Json.STRING v) => vcs_from_name v |
||
| 4870 | | _ => raise Fail "Strings expected in vcs array") |
||
| 4871 | vv |
||
| 4872 | | _ => raise Fail "Array expected for vcs", |
||
| 4873 | remote_spec = {
|
||
| 4874 | anon = lookup_optional_string pjson ["anonymous"], |
||
| 4875 | auth = lookup_optional_string pjson ["authenticated"] |
||
| 4876 | } |
||
| 4877 | } |
||
| 4878 | val loaded = |
||
| 4879 | case lookup_optional json ["services"] of |
||
| 4880 | NONE => [] |
||
| 4881 | | SOME (Json.OBJECT pl) => map (fn (k, v) => load v k) pl |
||
| 4882 | | _ => raise Fail "Object expected for services in config" |
||
| 4883 | val newly_loaded = |
||
| 4884 | List.filter (fn p => not (List.exists (fn pp => #service p = |
||
| 4885 | #service pp) |
||
| 4886 | previously_loaded)) |
||
| 4887 | loaded |
||
| 4888 | in |
||
| 4889 | previously_loaded @ newly_loaded |
||
| 4890 | end |
||
| 4891 | |||
| 4892 | fun load_providers json = |
||
| 4893 | load_more_providers known_providers json |
||
| 4894 | |||
| 4895 | fun expand_spec spec { vcs, service, owner, repo } login =
|
||
| 4896 | (* ugly *) |
||
| 4897 | let fun replace str = |
||
| 4898 | case str of |
||
| 4899 | "vcs" => vcs_name vcs |
||
| 4900 | | "service" => service |
||
| 4901 | | "owner" => |
||
| 4902 | (case owner of |
||
| 4903 | SOME ostr => ostr |
||
| 4904 | | NONE => raise Fail ("Owner not specified for service " ^
|
||
| 4905 | service)) |
||
| 4906 | | "repository" => repo |
||
| 4907 | | "account" => |
||
| 4908 | (case login of |
||
| 4909 | SOME acc => acc |
||
| 4910 | | NONE => raise Fail ("Account not given for service " ^
|
||
| 4911 | service)) |
||
| 4912 | | other => raise Fail ("Unknown variable \"" ^ other ^
|
||
| 4913 | "\" in spec for service " ^ service) |
||
| 4914 | fun expand' acc sstr = |
||
| 4915 | case Substring.splitl (fn c => c <> #"{") sstr of
|
||
| 4916 | (pfx, sfx) => |
||
| 4917 | if Substring.isEmpty sfx |
||
| 4918 | then rev (pfx :: acc) |
||
| 4919 | else |
||
| 4920 | case Substring.splitl (fn c => c <> #"}") sfx of |
||
| 4921 | (tok, remainder) => |
||
| 4922 | if Substring.isEmpty remainder |
||
| 4923 | then rev (tok :: pfx :: acc) |
||
| 4924 | else let val replacement = |
||
| 4925 | replace |
||
| 4926 | (* tok begins with "{": *)
|
||
| 4927 | (Substring.string |
||
| 4928 | (Substring.triml 1 tok)) |
||
| 4929 | in |
||
| 4930 | expand' (Substring.full replacement :: |
||
| 4931 | pfx :: acc) |
||
| 4932 | (* remainder begins with "}": *) |
||
| 4933 | (Substring.triml 1 remainder) |
||
| 4934 | end |
||
| 4935 | in |
||
| 4936 | Substring.concat (expand' [] (Substring.full spec)) |
||
| 4937 | end |
||
| 4938 | |||
| 4939 | fun provider_url req login providers = |
||
| 4940 | case providers of |
||
| 4941 | [] => raise Fail ("Unknown service \"" ^ (#service req) ^
|
||
| 4942 | "\" for vcs \"" ^ (vcs_name (#vcs req)) ^ "\"") |
||
| 4943 | | ({ service, supports, remote_spec : remote_spec } :: rest) =>
|
||
| 4944 | if service <> (#service req) orelse |
||
| 4945 | not (List.exists (fn v => v = (#vcs req)) supports) |
||
| 4946 | then provider_url req login rest |
||
| 4947 | else |
||
| 4948 | case (login, #auth remote_spec, #anon remote_spec) of |
||
| 4949 | (SOME _, SOME auth, _) => expand_spec auth req login |
||
| 4950 | | (SOME _, _, SOME anon) => expand_spec anon req NONE |
||
| 4951 | | (NONE, _, SOME anon) => expand_spec anon req NONE |
||
| 4952 | | _ => raise Fail ("No suitable anonymous or authenticated " ^
|
||
| 4953 | "URL spec provided for service \"" ^ |
||
| 4954 | service ^ "\"") |
||
| 4955 | |||
| 4956 | fun login_for ({ accounts, ... } : context) service =
|
||
| 4957 | case List.find (fn a => service = #service a) accounts of |
||
| 4958 | SOME { login, ... } => SOME login
|
||
| 4959 | | NONE => NONE |
||
| 4960 | |||
| 4961 | fun reponame_for path = |
||
| 4962 | case String.tokens (fn c => c = #"/") path of |
||
| 4963 | [] => raise Fail "Non-empty library path required" |
||
| 4964 | | toks => hd (rev toks) |
||
| 4965 | |||
| 4966 | fun remote_url (context : context) vcs source libname = |
||
| 4967 | case source of |
||
| 4968 | URL_SOURCE u => u |
||
| 4969 | | SERVICE_SOURCE { service, owner, repo } =>
|
||
| 4970 | provider_url { vcs = vcs,
|
||
| 4971 | service = service, |
||
| 4972 | owner = owner, |
||
| 4973 | repo = case repo of |
||
| 4974 | SOME r => r |
||
| 4975 | | NONE => reponame_for libname } |
||
| 4976 | (login_for context service) |
||
| 4977 | (#providers context) |
||
| 4978 | end |
||
| 4979 | |||
| 4980 | structure HgControl :> VCS_CONTROL = struct |
||
| 4981 | |||
| 4982 | (* Pulls always use an explicit URL, never just the default |
||
| 4983 | remote, in order to ensure we update properly if the location |
||
| 4984 | given in the project file changes. *) |
||
| 4985 | |||
| 4986 | type vcsstate = { id: string, modified: bool,
|
||
| 4987 | branch: string, tags: string list } |
||
| 4988 | |||
| 4989 | 76:a6c9a0ca493e | Chris | val hg_program = "hg" |
| 4990 | |||
| 4991 | 70:2d3e1d1f99c0 | Chris | val hg_args = [ "--config", "ui.interactive=true", |
| 4992 | "--config", "ui.merge=:merge" ] |
||
| 4993 | |||
| 4994 | fun hg_command context libname args = |
||
| 4995 | 76:a6c9a0ca493e | Chris | FileBits.command context libname (hg_program :: hg_args @ args) |
| 4996 | 70:2d3e1d1f99c0 | Chris | |
| 4997 | fun hg_command_output context libname args = |
||
| 4998 | 76:a6c9a0ca493e | Chris | FileBits.command_output context libname (hg_program :: hg_args @ args) |
| 4999 | |||
| 5000 | fun is_working context = |
||
| 5001 | case hg_command_output context "" ["--version"] of |
||
| 5002 | OK "" => OK false |
||
| 5003 | | OK _ => OK true |
||
| 5004 | | ERROR e => ERROR e |
||
| 5005 | |||
| 5006 | 70:2d3e1d1f99c0 | Chris | fun exists context libname = |
| 5007 | OK (OS.FileSys.isDir (FileBits.subpath context libname ".hg")) |
||
| 5008 | handle _ => OK false |
||
| 5009 | |||
| 5010 | fun remote_for context (libname, source) = |
||
| 5011 | Provider.remote_url context HG source libname |
||
| 5012 | |||
| 5013 | fun current_state context libname : vcsstate result = |
||
| 5014 | let fun is_branch text = text <> "" andalso #"(" = hd (explode text)
|
||
| 5015 | and extract_branch b = |
||
| 5016 | if is_branch b (* need to remove enclosing parens *) |
||
| 5017 | then (implode o rev o tl o rev o tl o explode) b |
||
| 5018 | else "default" |
||
| 5019 | and is_modified id = id <> "" andalso #"+" = hd (rev (explode id)) |
||
| 5020 | and extract_id id = |
||
| 5021 | if is_modified id (* need to remove trailing "+" *) |
||
| 5022 | then (implode o rev o tl o rev o explode) id |
||
| 5023 | else id |
||
| 5024 | and split_tags tags = String.tokens (fn c => c = #"/") tags |
||
| 5025 | and state_for (id, branch, tags) = |
||
| 5026 | OK { id = extract_id id,
|
||
| 5027 | modified = is_modified id, |
||
| 5028 | branch = extract_branch branch, |
||
| 5029 | tags = split_tags tags } |
||
| 5030 | in |
||
| 5031 | case hg_command_output context libname ["id"] of |
||
| 5032 | ERROR e => ERROR e |
||
| 5033 | | OK out => |
||
| 5034 | case String.tokens (fn x => x = #" ") out of |
||
| 5035 | [id, branch, tags] => state_for (id, branch, tags) |
||
| 5036 | | [id, other] => if is_branch other |
||
| 5037 | then state_for (id, other, "") |
||
| 5038 | else state_for (id, "", other) |
||
| 5039 | | [id] => state_for (id, "", "") |
||
| 5040 | | _ => ERROR ("Unexpected output from hg id: " ^ out)
|
||
| 5041 | end |
||
| 5042 | |||
| 5043 | fun branch_name branch = case branch of |
||
| 5044 | DEFAULT_BRANCH => "default" |
||
| 5045 | | BRANCH "" => "default" |
||
| 5046 | | BRANCH b => b |
||
| 5047 | |||
| 5048 | fun id_of context libname = |
||
| 5049 | case current_state context libname of |
||
| 5050 | ERROR e => ERROR e |
||
| 5051 | | OK { id, ... } => OK id
|
||
| 5052 | |||
| 5053 | fun is_at context (libname, id_or_tag) = |
||
| 5054 | case current_state context libname of |
||
| 5055 | ERROR e => ERROR e |
||
| 5056 | | OK { id, tags, ... } =>
|
||
| 5057 | OK (String.isPrefix id_or_tag id orelse |
||
| 5058 | String.isPrefix id id_or_tag orelse |
||
| 5059 | List.exists (fn t => t = id_or_tag) tags) |
||
| 5060 | |||
| 5061 | fun is_on_branch context (libname, b) = |
||
| 5062 | case current_state context libname of |
||
| 5063 | ERROR e => ERROR e |
||
| 5064 | | OK { branch, ... } => OK (branch = branch_name b)
|
||
| 5065 | |||
| 5066 | fun is_newest_locally context (libname, branch) = |
||
| 5067 | case hg_command_output context libname |
||
| 5068 | ["log", "-l1", |
||
| 5069 | "-b", branch_name branch, |
||
| 5070 | "--template", "{node}"] of
|
||
| 5071 | ERROR e => OK false (* desired branch does not exist *) |
||
| 5072 | | OK newest_in_repo => is_at context (libname, newest_in_repo) |
||
| 5073 | |||
| 5074 | fun pull context (libname, source) = |
||
| 5075 | let val url = remote_for context (libname, source) |
||
| 5076 | in |
||
| 5077 | hg_command context libname |
||
| 5078 | (if FileBits.verbose () |
||
| 5079 | then ["pull", url] |
||
| 5080 | else ["pull", "-q", url]) |
||
| 5081 | end |
||
| 5082 | |||
| 5083 | fun is_newest context (libname, source, branch) = |
||
| 5084 | case is_newest_locally context (libname, branch) of |
||
| 5085 | ERROR e => ERROR e |
||
| 5086 | | OK false => OK false |
||
| 5087 | | OK true => |
||
| 5088 | case pull context (libname, source) of |
||
| 5089 | ERROR e => ERROR e |
||
| 5090 | | _ => is_newest_locally context (libname, branch) |
||
| 5091 | |||
| 5092 | fun is_modified_locally context libname = |
||
| 5093 | case current_state context libname of |
||
| 5094 | ERROR e => ERROR e |
||
| 5095 | | OK { modified, ... } => OK modified
|
||
| 5096 | |||
| 5097 | fun checkout context (libname, source, branch) = |
||
| 5098 | let val url = remote_for context (libname, source) |
||
| 5099 | in |
||
| 5100 | (* make the lib dir rather than just the ext dir, since |
||
| 5101 | the lib dir might be nested and hg will happily check |
||
| 5102 | out into an existing empty dir anyway *) |
||
| 5103 | case FileBits.mkpath (FileBits.libpath context libname) of |
||
| 5104 | ERROR e => ERROR e |
||
| 5105 | | _ => hg_command context "" |
||
| 5106 | ["clone", "-u", branch_name branch, |
||
| 5107 | url, libname] |
||
| 5108 | end |
||
| 5109 | |||
| 5110 | fun update context (libname, source, branch) = |
||
| 5111 | let val pull_result = pull context (libname, source) |
||
| 5112 | in |
||
| 5113 | case hg_command context libname ["update", branch_name branch] of |
||
| 5114 | ERROR e => ERROR e |
||
| 5115 | | _ => |
||
| 5116 | case pull_result of |
||
| 5117 | ERROR e => ERROR e |
||
| 5118 | | _ => OK () |
||
| 5119 | end |
||
| 5120 | |||
| 5121 | fun update_to context (libname, _, "") = |
||
| 5122 | ERROR "Non-empty id (tag or revision id) required for update_to" |
||
| 5123 | | update_to context (libname, source, id) = |
||
| 5124 | let val pull_result = pull context (libname, source) |
||
| 5125 | in |
||
| 5126 | case hg_command context libname ["update", "-r", id] of |
||
| 5127 | OK _ => OK () |
||
| 5128 | | ERROR e => |
||
| 5129 | case pull_result of |
||
| 5130 | ERROR e' => ERROR e' (* this was the ur-error *) |
||
| 5131 | | _ => ERROR e |
||
| 5132 | end |
||
| 5133 | |||
| 5134 | fun copy_url_for context libname = |
||
| 5135 | OK (FileBits.file_url (FileBits.libpath context libname)) |
||
| 5136 | |||
| 5137 | end |
||
| 5138 | |||
| 5139 | structure GitControl :> VCS_CONTROL = struct |
||
| 5140 | |||
| 5141 | (* With Git repos we always operate in detached HEAD state. Even |
||
| 5142 | the master branch is checked out using a remote reference |
||
| 5143 | 76:a6c9a0ca493e | Chris | (repoint/master). The remote we use is always named repoint, and we |
| 5144 | 70:2d3e1d1f99c0 | Chris | update it to the expected URL each time we fetch, in order to |
| 5145 | ensure we update properly if the location given in the project |
||
| 5146 | file changes. The origin remote is unused. *) |
||
| 5147 | |||
| 5148 | 76:a6c9a0ca493e | Chris | val git_program = "git" |
| 5149 | |||
| 5150 | 70:2d3e1d1f99c0 | Chris | fun git_command context libname args = |
| 5151 | 76:a6c9a0ca493e | Chris | FileBits.command context libname (git_program :: args) |
| 5152 | 70:2d3e1d1f99c0 | Chris | |
| 5153 | fun git_command_output context libname args = |
||
| 5154 | 76:a6c9a0ca493e | Chris | FileBits.command_output context libname (git_program :: args) |
| 5155 | |||
| 5156 | fun is_working context = |
||
| 5157 | case git_command_output context "" ["--version"] of |
||
| 5158 | OK "" => OK false |
||
| 5159 | | OK _ => OK true |
||
| 5160 | | ERROR e => ERROR e |
||
| 5161 | 70:2d3e1d1f99c0 | Chris | |
| 5162 | fun exists context libname = |
||
| 5163 | OK (OS.FileSys.isDir (FileBits.subpath context libname ".git")) |
||
| 5164 | handle _ => OK false |
||
| 5165 | |||
| 5166 | fun remote_for context (libname, source) = |
||
| 5167 | Provider.remote_url context GIT source libname |
||
| 5168 | |||
| 5169 | fun branch_name branch = case branch of |
||
| 5170 | DEFAULT_BRANCH => "master" |
||
| 5171 | | BRANCH "" => "master" |
||
| 5172 | | BRANCH b => b |
||
| 5173 | |||
| 5174 | 76:a6c9a0ca493e | Chris | val our_remote = "repoint" |
| 5175 | 70:2d3e1d1f99c0 | Chris | |
| 5176 | fun remote_branch_name branch = our_remote ^ "/" ^ branch_name branch |
||
| 5177 | |||
| 5178 | fun checkout context (libname, source, branch) = |
||
| 5179 | let val url = remote_for context (libname, source) |
||
| 5180 | in |
||
| 5181 | (* make the lib dir rather than just the ext dir, since |
||
| 5182 | the lib dir might be nested and git will happily check |
||
| 5183 | out into an existing empty dir anyway *) |
||
| 5184 | case FileBits.mkpath (FileBits.libpath context libname) of |
||
| 5185 | OK () => git_command context "" |
||
| 5186 | ["clone", "--origin", our_remote, |
||
| 5187 | "--branch", branch_name branch, |
||
| 5188 | url, libname] |
||
| 5189 | | ERROR e => ERROR e |
||
| 5190 | end |
||
| 5191 | |||
| 5192 | fun add_our_remote context (libname, source) = |
||
| 5193 | (* When we do the checkout ourselves (above), we add the |
||
| 5194 | remote at the same time. But if the repo was cloned by |
||
| 5195 | someone else, we'll need to do it after the fact. Git |
||
| 5196 | doesn't seem to have a means to add a remote or change its |
||
| 5197 | url if it already exists; seems we have to do this: *) |
||
| 5198 | let val url = remote_for context (libname, source) |
||
| 5199 | in |
||
| 5200 | case git_command context libname |
||
| 5201 | ["remote", "set-url", our_remote, url] of |
||
| 5202 | OK () => OK () |
||
| 5203 | | ERROR e => git_command context libname |
||
| 5204 | ["remote", "add", "-f", our_remote, url] |
||
| 5205 | end |
||
| 5206 | |||
| 5207 | (* NB git rev-parse HEAD shows revision id of current checkout; |
||
| 5208 | git rev-list -1 <tag> shows revision id of revision with that tag *) |
||
| 5209 | |||
| 5210 | fun id_of context libname = |
||
| 5211 | git_command_output context libname ["rev-parse", "HEAD"] |
||
| 5212 | |||
| 5213 | fun is_at context (libname, id_or_tag) = |
||
| 5214 | case id_of context libname of |
||
| 5215 | ERROR e => OK false (* HEAD nonexistent, expected in empty repo *) |
||
| 5216 | | OK id => |
||
| 5217 | if String.isPrefix id_or_tag id orelse |
||
| 5218 | String.isPrefix id id_or_tag |
||
| 5219 | then OK true |
||
| 5220 | else is_at_tag context (libname, id, id_or_tag) |
||
| 5221 | |||
| 5222 | and is_at_tag context (libname, id, tag) = |
||
| 5223 | (* For annotated tags (with message) show-ref returns the tag |
||
| 5224 | object ref rather than that of the revision being tagged; |
||
| 5225 | we need the subsequent rev-list to chase that up. In fact |
||
| 5226 | the rev-list on its own is enough to get us the id direct |
||
| 5227 | from the tag name, but it fails with an error if the tag |
||
| 5228 | doesn't exist, whereas we want to handle that quietly in |
||
| 5229 | case the tag simply hasn't been pulled yet *) |
||
| 5230 | case git_command_output context libname |
||
| 5231 | ["show-ref", "refs/tags/" ^ tag, "--"] of |
||
| 5232 | OK "" => OK false (* Not a tag *) |
||
| 5233 | | ERROR _ => OK false |
||
| 5234 | | OK s => |
||
| 5235 | let val tag_ref = hd (String.tokens (fn c => c = #" ") s) |
||
| 5236 | in |
||
| 5237 | case git_command_output context libname |
||
| 5238 | ["rev-list", "-1", tag_ref] of |
||
| 5239 | OK tagged => OK (id = tagged) |
||
| 5240 | | ERROR _ => OK false |
||
| 5241 | end |
||
| 5242 | |||
| 5243 | fun branch_tip context (libname, branch) = |
||
| 5244 | (* We don't have access to the source info or the network |
||
| 5245 | here, as this is used by status (e.g. via is_on_branch) as |
||
| 5246 | well as review. It's possible the remote branch won't exist, |
||
| 5247 | e.g. if the repo was checked out by something other than |
||
| 5248 | 76:a6c9a0ca493e | Chris | Repoint, and if that's the case, we can't add it here; we'll |
| 5249 | 70:2d3e1d1f99c0 | Chris | just have to fail, since checking against local branches |
| 5250 | instead could produce the wrong result. *) |
||
| 5251 | git_command_output context libname |
||
| 5252 | ["rev-list", "-1", |
||
| 5253 | remote_branch_name branch, "--"] |
||
| 5254 | |||
| 5255 | fun is_newest_locally context (libname, branch) = |
||
| 5256 | case branch_tip context (libname, branch) of |
||
| 5257 | ERROR e => OK false |
||
| 5258 | | OK rev => is_at context (libname, rev) |
||
| 5259 | |||
| 5260 | fun is_on_branch context (libname, branch) = |
||
| 5261 | case branch_tip context (libname, branch) of |
||
| 5262 | ERROR e => OK false |
||
| 5263 | | OK rev => |
||
| 5264 | case is_at context (libname, rev) of |
||
| 5265 | ERROR e => ERROR e |
||
| 5266 | | OK true => OK true |
||
| 5267 | | OK false => |
||
| 5268 | case git_command context libname |
||
| 5269 | ["merge-base", "--is-ancestor", |
||
| 5270 | "HEAD", remote_branch_name branch] of |
||
| 5271 | ERROR e => OK false (* cmd returns non-zero for no *) |
||
| 5272 | | _ => OK true |
||
| 5273 | |||
| 5274 | fun fetch context (libname, source) = |
||
| 5275 | case add_our_remote context (libname, source) of |
||
| 5276 | ERROR e => ERROR e |
||
| 5277 | | _ => git_command context libname ["fetch", our_remote] |
||
| 5278 | |||
| 5279 | fun is_newest context (libname, source, branch) = |
||
| 5280 | case add_our_remote context (libname, source) of |
||
| 5281 | ERROR e => ERROR e |
||
| 5282 | | OK () => |
||
| 5283 | case is_newest_locally context (libname, branch) of |
||
| 5284 | ERROR e => ERROR e |
||
| 5285 | | OK false => OK false |
||
| 5286 | | OK true => |
||
| 5287 | case fetch context (libname, source) of |
||
| 5288 | ERROR e => ERROR e |
||
| 5289 | | _ => is_newest_locally context (libname, branch) |
||
| 5290 | |||
| 5291 | fun is_modified_locally context libname = |
||
| 5292 | case git_command_output context libname ["status", "--porcelain"] of |
||
| 5293 | ERROR e => ERROR e |
||
| 5294 | | OK "" => OK false |
||
| 5295 | | OK _ => OK true |
||
| 5296 | |||
| 5297 | (* This function updates to the latest revision on a branch rather |
||
| 5298 | than to a specific id or tag. We can't just checkout the given |
||
| 5299 | branch, as that will succeed even if the branch isn't up to |
||
| 5300 | date. We could checkout the branch and then fetch and merge, |
||
| 5301 | but it's perhaps cleaner not to maintain a local branch at all, |
||
| 5302 | but instead checkout the remote branch as a detached head. *) |
||
| 5303 | |||
| 5304 | fun update context (libname, source, branch) = |
||
| 5305 | case fetch context (libname, source) of |
||
| 5306 | ERROR e => ERROR e |
||
| 5307 | | _ => |
||
| 5308 | case git_command context libname ["checkout", "--detach", |
||
| 5309 | remote_branch_name branch] of |
||
| 5310 | ERROR e => ERROR e |
||
| 5311 | | _ => OK () |
||
| 5312 | |||
| 5313 | (* This function is dealing with a specific id or tag, so if we |
||
| 5314 | can successfully check it out (detached) then that's all we |
||
| 5315 | need to do, regardless of whether fetch succeeded or not. We do |
||
| 5316 | attempt the fetch first, though, purely in order to avoid ugly |
||
| 5317 | error messages in the common case where we're being asked to |
||
| 5318 | update to a new pin (from the lock file) that hasn't been |
||
| 5319 | fetched yet. *) |
||
| 5320 | |||
| 5321 | fun update_to context (libname, _, "") = |
||
| 5322 | ERROR "Non-empty id (tag or revision id) required for update_to" |
||
| 5323 | | update_to context (libname, source, id) = |
||
| 5324 | let val fetch_result = fetch context (libname, source) |
||
| 5325 | in |
||
| 5326 | case git_command context libname ["checkout", "--detach", id] of |
||
| 5327 | OK _ => OK () |
||
| 5328 | | ERROR e => |
||
| 5329 | case fetch_result of |
||
| 5330 | ERROR e' => ERROR e' (* this was the ur-error *) |
||
| 5331 | | _ => ERROR e |
||
| 5332 | end |
||
| 5333 | |||
| 5334 | fun copy_url_for context libname = |
||
| 5335 | OK (FileBits.file_url (FileBits.libpath context libname)) |
||
| 5336 | |||
| 5337 | end |
||
| 5338 | |||
| 5339 | (* SubXml - A parser for a subset of XML |
||
| 5340 | 76:a6c9a0ca493e | Chris | https://bitbucket.org/cannam/sml-subxml |
| 5341 | 70:2d3e1d1f99c0 | Chris | Copyright 2018 Chris Cannam. BSD licence. |
| 5342 | *) |
||
| 5343 | |||
| 5344 | signature SUBXML = sig |
||
| 5345 | |||
| 5346 | datatype node = ELEMENT of { name : string, children : node list }
|
||
| 5347 | | ATTRIBUTE of { name : string, value : string }
|
||
| 5348 | | TEXT of string |
||
| 5349 | | CDATA of string |
||
| 5350 | | COMMENT of string |
||
| 5351 | |||
| 5352 | datatype document = DOCUMENT of { name : string, children : node list }
|
||
| 5353 | |||
| 5354 | datatype 'a result = OK of 'a |
||
| 5355 | | ERROR of string |
||
| 5356 | |||
| 5357 | val parse : string -> document result |
||
| 5358 | val serialise : document -> string |
||
| 5359 | |||
| 5360 | end |
||
| 5361 | |||
| 5362 | structure SubXml :> SUBXML = struct |
||
| 5363 | |||
| 5364 | datatype node = ELEMENT of { name : string, children : node list }
|
||
| 5365 | | ATTRIBUTE of { name : string, value : string }
|
||
| 5366 | | TEXT of string |
||
| 5367 | | CDATA of string |
||
| 5368 | | COMMENT of string |
||
| 5369 | |||
| 5370 | datatype document = DOCUMENT of { name : string, children : node list }
|
||
| 5371 | |||
| 5372 | datatype 'a result = OK of 'a |
||
| 5373 | | ERROR of string |
||
| 5374 | |||
| 5375 | structure T = struct |
||
| 5376 | datatype token = ANGLE_L |
||
| 5377 | | ANGLE_R |
||
| 5378 | | ANGLE_SLASH_L |
||
| 5379 | | SLASH_ANGLE_R |
||
| 5380 | | EQUAL |
||
| 5381 | | NAME of string |
||
| 5382 | | TEXT of string |
||
| 5383 | | CDATA of string |
||
| 5384 | | COMMENT of string |
||
| 5385 | |||
| 5386 | fun name t = |
||
| 5387 | case t of ANGLE_L => "<" |
||
| 5388 | | ANGLE_R => ">" |
||
| 5389 | | ANGLE_SLASH_L => "</" |
||
| 5390 | | SLASH_ANGLE_R => "/>" |
||
| 5391 | | EQUAL => "=" |
||
| 5392 | | NAME s => "name \"" ^ s ^ "\"" |
||
| 5393 | | TEXT s => "text" |
||
| 5394 | | CDATA _ => "CDATA section" |
||
| 5395 | | COMMENT _ => "comment" |
||
| 5396 | end |
||
| 5397 | |||
| 5398 | structure Lex :> sig |
||
| 5399 | val lex : string -> T.token list result |
||
| 5400 | end = struct |
||
| 5401 | |||
| 5402 | fun error pos text = |
||
| 5403 | ERROR (text ^ " at character position " ^ Int.toString (pos-1)) |
||
| 5404 | fun tokenError pos token = |
||
| 5405 | error pos ("Unexpected token '" ^ Char.toString token ^ "'")
|
||
| 5406 | |||
| 5407 | val nameEnd = explode " \t\n\r\"'</>!=?" |
||
| 5408 | |||
| 5409 | fun quoted quote pos acc cc = |
||
| 5410 | let fun quoted' pos text [] = |
||
| 5411 | error pos "Document ends during quoted string" |
||
| 5412 | | quoted' pos text (x::xs) = |
||
| 5413 | if x = quote |
||
| 5414 | then OK (rev text, xs, pos+1) |
||
| 5415 | else quoted' (pos+1) (x::text) xs |
||
| 5416 | in |
||
| 5417 | case quoted' pos [] cc of |
||
| 5418 | ERROR e => ERROR e |
||
| 5419 | | OK (text, rest, newpos) => |
||
| 5420 | inside newpos (T.TEXT (implode text) :: acc) rest |
||
| 5421 | end |
||
| 5422 | |||
| 5423 | and name first pos acc cc = |
||
| 5424 | let fun name' pos text [] = |
||
| 5425 | error pos "Document ends during name" |
||
| 5426 | | name' pos text (x::xs) = |
||
| 5427 | if List.find (fn c => c = x) nameEnd <> NONE |
||
| 5428 | then OK (rev text, (x::xs), pos) |
||
| 5429 | else name' (pos+1) (x::text) xs |
||
| 5430 | in |
||
| 5431 | case name' (pos-1) [] (first::cc) of |
||
| 5432 | ERROR e => ERROR e |
||
| 5433 | | OK ([], [], pos) => error pos "Document ends before name" |
||
| 5434 | | OK ([], (x::xs), pos) => tokenError pos x |
||
| 5435 | | OK (text, rest, pos) => |
||
| 5436 | inside pos (T.NAME (implode text) :: acc) rest |
||
| 5437 | end |
||
| 5438 | |||
| 5439 | and comment pos acc cc = |
||
| 5440 | let fun comment' pos text cc = |
||
| 5441 | case cc of |
||
| 5442 | #"-" :: #"-" :: #">" :: xs => OK (rev text, xs, pos+3) |
||
| 5443 | | x :: xs => comment' (pos+1) (x::text) xs |
||
| 5444 | | [] => error pos "Document ends during comment" |
||
| 5445 | in |
||
| 5446 | case comment' pos [] cc of |
||
| 5447 | ERROR e => ERROR e |
||
| 5448 | | OK (text, rest, pos) => |
||
| 5449 | outside pos (T.COMMENT (implode text) :: acc) rest |
||
| 5450 | end |
||
| 5451 | |||
| 5452 | and instruction pos acc cc = |
||
| 5453 | case cc of |
||
| 5454 | #"?" :: #">" :: xs => outside (pos+2) acc xs |
||
| 5455 | | #">" :: _ => tokenError pos #">" |
||
| 5456 | | x :: xs => instruction (pos+1) acc xs |
||
| 5457 | | [] => error pos "Document ends during processing instruction" |
||
| 5458 | |||
| 5459 | and cdata pos acc cc = |
||
| 5460 | let fun cdata' pos text cc = |
||
| 5461 | case cc of |
||
| 5462 | #"]" :: #"]" :: #">" :: xs => OK (rev text, xs, pos+3) |
||
| 5463 | | x :: xs => cdata' (pos+1) (x::text) xs |
||
| 5464 | | [] => error pos "Document ends during CDATA section" |
||
| 5465 | in |
||
| 5466 | case cdata' pos [] cc of |
||
| 5467 | ERROR e => ERROR e |
||
| 5468 | | OK (text, rest, pos) => |
||
| 5469 | outside pos (T.CDATA (implode text) :: acc) rest |
||
| 5470 | end |
||
| 5471 | |||
| 5472 | and doctype pos acc cc = |
||
| 5473 | case cc of |
||
| 5474 | #">" :: xs => outside (pos+1) acc xs |
||
| 5475 | | x :: xs => doctype (pos+1) acc xs |
||
| 5476 | | [] => error pos "Document ends during DOCTYPE" |
||
| 5477 | |||
| 5478 | and declaration pos acc cc = |
||
| 5479 | case cc of |
||
| 5480 | #"-" :: #"-" :: xs => |
||
| 5481 | comment (pos+2) acc xs |
||
| 5482 | | #"[" :: #"C" :: #"D" :: #"A" :: #"T" :: #"A" :: #"[" :: xs => |
||
| 5483 | cdata (pos+7) acc xs |
||
| 5484 | | #"D" :: #"O" :: #"C" :: #"T" :: #"Y" :: #"P" :: #"E" :: xs => |
||
| 5485 | doctype (pos+7) acc xs |
||
| 5486 | | [] => error pos "Document ends during declaration" |
||
| 5487 | | _ => error pos "Unsupported declaration type" |
||
| 5488 | |||
| 5489 | and left pos acc cc = |
||
| 5490 | case cc of |
||
| 5491 | #"/" :: xs => inside (pos+1) (T.ANGLE_SLASH_L :: acc) xs |
||
| 5492 | | #"!" :: xs => declaration (pos+1) acc xs |
||
| 5493 | | #"?" :: xs => instruction (pos+1) acc xs |
||
| 5494 | | xs => inside pos (T.ANGLE_L :: acc) xs |
||
| 5495 | |||
| 5496 | and slash pos acc cc = |
||
| 5497 | case cc of |
||
| 5498 | #">" :: xs => outside (pos+1) (T.SLASH_ANGLE_R :: acc) xs |
||
| 5499 | | x :: _ => tokenError pos x |
||
| 5500 | | [] => error pos "Document ends before element closed" |
||
| 5501 | |||
| 5502 | and close pos acc xs = outside pos (T.ANGLE_R :: acc) xs |
||
| 5503 | |||
| 5504 | and equal pos acc xs = inside pos (T.EQUAL :: acc) xs |
||
| 5505 | |||
| 5506 | and outside pos acc [] = OK acc |
||
| 5507 | | outside pos acc cc = |
||
| 5508 | let fun textOf text = T.TEXT (implode (rev text)) |
||
| 5509 | fun outside' pos [] acc [] = OK acc |
||
| 5510 | | outside' pos text acc [] = OK (textOf text :: acc) |
||
| 5511 | | outside' pos text acc (x::xs) = |
||
| 5512 | case x of |
||
| 5513 | #"<" => if text = [] |
||
| 5514 | then left (pos+1) acc xs |
||
| 5515 | else left (pos+1) (textOf text :: acc) xs |
||
| 5516 | | x => outside' (pos+1) (x::text) acc xs |
||
| 5517 | in |
||
| 5518 | outside' pos [] acc cc |
||
| 5519 | end |
||
| 5520 | |||
| 5521 | and inside pos acc [] = error pos "Document ends within tag" |
||
| 5522 | | inside pos acc (#"<"::_) = tokenError pos #"<" |
||
| 5523 | | inside pos acc (x::xs) = |
||
| 5524 | (case x of |
||
| 5525 | #" " => inside | #"\t" => inside |
||
| 5526 | | #"\n" => inside | #"\r" => inside |
||
| 5527 | | #"\"" => quoted x | #"'" => quoted x |
||
| 5528 | | #"/" => slash | #">" => close | #"=" => equal |
||
| 5529 | | x => name x) (pos+1) acc xs |
||
| 5530 | |||
| 5531 | fun lex str = |
||
| 5532 | case outside 1 [] (explode str) of |
||
| 5533 | ERROR e => ERROR e |
||
| 5534 | | OK tokens => OK (rev tokens) |
||
| 5535 | end |
||
| 5536 | |||
| 5537 | structure Parse :> sig |
||
| 5538 | val parse : string -> document result |
||
| 5539 | end = struct |
||
| 5540 | |||
| 5541 | fun show [] = "end of input" |
||
| 5542 | | show (tok :: _) = T.name tok |
||
| 5543 | |||
| 5544 | fun error toks text = ERROR (text ^ " before " ^ show toks) |
||
| 5545 | |||
| 5546 | fun attribute elt name toks = |
||
| 5547 | case toks of |
||
| 5548 | T.EQUAL :: T.TEXT value :: xs => |
||
| 5549 | namedElement {
|
||
| 5550 | name = #name elt, |
||
| 5551 | children = ATTRIBUTE { name = name, value = value } ::
|
||
| 5552 | #children elt |
||
| 5553 | } xs |
||
| 5554 | | T.EQUAL :: xs => error xs "Expected attribute value" |
||
| 5555 | | toks => error toks "Expected attribute assignment" |
||
| 5556 | |||
| 5557 | and content elt toks = |
||
| 5558 | case toks of |
||
| 5559 | T.ANGLE_SLASH_L :: T.NAME n :: T.ANGLE_R :: xs => |
||
| 5560 | if n = #name elt |
||
| 5561 | then OK (elt, xs) |
||
| 5562 | else ERROR ("Closing tag </" ^ n ^ "> " ^
|
||
| 5563 | "does not match opening <" ^ #name elt ^ ">") |
||
| 5564 | | T.TEXT text :: xs => |
||
| 5565 | content {
|
||
| 5566 | name = #name elt, |
||
| 5567 | children = TEXT text :: #children elt |
||
| 5568 | } xs |
||
| 5569 | | T.CDATA text :: xs => |
||
| 5570 | content {
|
||
| 5571 | name = #name elt, |
||
| 5572 | children = CDATA text :: #children elt |
||
| 5573 | } xs |
||
| 5574 | | T.COMMENT text :: xs => |
||
| 5575 | content {
|
||
| 5576 | name = #name elt, |
||
| 5577 | children = COMMENT text :: #children elt |
||
| 5578 | } xs |
||
| 5579 | | T.ANGLE_L :: xs => |
||
| 5580 | (case element xs of |
||
| 5581 | ERROR e => ERROR e |
||
| 5582 | | OK (child, xs) => |
||
| 5583 | content {
|
||
| 5584 | name = #name elt, |
||
| 5585 | children = ELEMENT child :: #children elt |
||
| 5586 | } xs) |
||
| 5587 | | tok :: xs => |
||
| 5588 | error xs ("Unexpected token " ^ T.name tok)
|
||
| 5589 | | [] => |
||
| 5590 | ERROR ("Document ends within element \"" ^ #name elt ^ "\"")
|
||
| 5591 | |||
| 5592 | and namedElement elt toks = |
||
| 5593 | case toks of |
||
| 5594 | T.SLASH_ANGLE_R :: xs => OK (elt, xs) |
||
| 5595 | | T.NAME name :: xs => attribute elt name xs |
||
| 5596 | | T.ANGLE_R :: xs => content elt xs |
||
| 5597 | | x :: xs => error xs ("Unexpected token " ^ T.name x)
|
||
| 5598 | | [] => ERROR "Document ends within opening tag" |
||
| 5599 | |||
| 5600 | and element toks = |
||
| 5601 | case toks of |
||
| 5602 | T.NAME name :: xs => |
||
| 5603 | (case namedElement { name = name, children = [] } xs of
|
||
| 5604 | ERROR e => ERROR e |
||
| 5605 | | OK ({ name, children }, xs) =>
|
||
| 5606 | OK ({ name = name, children = rev children }, xs))
|
||
| 5607 | | toks => error toks "Expected element name" |
||
| 5608 | |||
| 5609 | and document [] = ERROR "Empty document" |
||
| 5610 | | document (tok :: xs) = |
||
| 5611 | case tok of |
||
| 5612 | T.TEXT _ => document xs |
||
| 5613 | | T.COMMENT _ => document xs |
||
| 5614 | | T.ANGLE_L => |
||
| 5615 | (case element xs of |
||
| 5616 | ERROR e => ERROR e |
||
| 5617 | | OK (elt, []) => OK (DOCUMENT elt) |
||
| 5618 | | OK (elt, (T.TEXT _ :: xs)) => OK (DOCUMENT elt) |
||
| 5619 | | OK (elt, xs) => error xs "Extra data after document") |
||
| 5620 | | _ => error xs ("Unexpected token " ^ T.name tok)
|
||
| 5621 | |||
| 5622 | fun parse str = |
||
| 5623 | case Lex.lex str of |
||
| 5624 | ERROR e => ERROR e |
||
| 5625 | | OK tokens => document tokens |
||
| 5626 | end |
||
| 5627 | |||
| 5628 | structure Serialise :> sig |
||
| 5629 | val serialise : document -> string |
||
| 5630 | end = struct |
||
| 5631 | |||
| 5632 | fun attributes nodes = |
||
| 5633 | String.concatWith |
||
| 5634 | " " |
||
| 5635 | (map node (List.filter |
||
| 5636 | (fn ATTRIBUTE _ => true | _ => false) |
||
| 5637 | nodes)) |
||
| 5638 | |||
| 5639 | and nonAttributes nodes = |
||
| 5640 | String.concat |
||
| 5641 | (map node (List.filter |
||
| 5642 | (fn ATTRIBUTE _ => false | _ => true) |
||
| 5643 | nodes)) |
||
| 5644 | |||
| 5645 | and node n = |
||
| 5646 | case n of |
||
| 5647 | TEXT string => |
||
| 5648 | string |
||
| 5649 | | CDATA string => |
||
| 5650 | "<![CDATA[" ^ string ^ "]]>" |
||
| 5651 | | COMMENT string => |
||
| 5652 | "<!-- " ^ string ^ "-->" |
||
| 5653 | | ATTRIBUTE { name, value } =>
|
||
| 5654 | name ^ "=" ^ "\"" ^ value ^ "\"" (*!!!*) |
||
| 5655 | | ELEMENT { name, children } =>
|
||
| 5656 | "<" ^ name ^ |
||
| 5657 | (case (attributes children) of |
||
| 5658 | "" => "" |
||
| 5659 | | s => " " ^ s) ^ |
||
| 5660 | (case (nonAttributes children) of |
||
| 5661 | "" => "/>" |
||
| 5662 | | s => ">" ^ s ^ "</" ^ name ^ ">") |
||
| 5663 | |||
| 5664 | fun serialise (DOCUMENT { name, children }) =
|
||
| 5665 | "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" ^ |
||
| 5666 | node (ELEMENT { name = name, children = children })
|
||
| 5667 | end |
||
| 5668 | |||
| 5669 | val parse = Parse.parse |
||
| 5670 | val serialise = Serialise.serialise |
||
| 5671 | |||
| 5672 | end |
||
| 5673 | |||
| 5674 | |||
| 5675 | structure SvnControl :> VCS_CONTROL = struct |
||
| 5676 | |||
| 5677 | 76:a6c9a0ca493e | Chris | val svn_program = "svn" |
| 5678 | |||
| 5679 | 70:2d3e1d1f99c0 | Chris | fun svn_command context libname args = |
| 5680 | 76:a6c9a0ca493e | Chris | FileBits.command context libname (svn_program :: args) |
| 5681 | 70:2d3e1d1f99c0 | Chris | |
| 5682 | fun svn_command_output context libname args = |
||
| 5683 | 76:a6c9a0ca493e | Chris | FileBits.command_output context libname (svn_program :: args) |
| 5684 | 70:2d3e1d1f99c0 | Chris | |
| 5685 | fun svn_command_lines context libname args = |
||
| 5686 | case svn_command_output context libname args of |
||
| 5687 | ERROR e => ERROR e |
||
| 5688 | | OK s => OK (String.tokens (fn c => c = #"\n" orelse c = #"\r") s) |
||
| 5689 | |||
| 5690 | fun split_line_pair line = |
||
| 5691 | let fun strip_leading_ws str = case explode str of |
||
| 5692 | #" "::rest => implode rest |
||
| 5693 | | _ => str |
||
| 5694 | in |
||
| 5695 | case String.tokens (fn c => c = #":") line of |
||
| 5696 | [] => ("", "")
|
||
| 5697 | | first::rest => |
||
| 5698 | (first, strip_leading_ws (String.concatWith ":" rest)) |
||
| 5699 | end |
||
| 5700 | |||
| 5701 | 76:a6c9a0ca493e | Chris | fun is_working context = |
| 5702 | case svn_command_output context "" ["--version"] of |
||
| 5703 | OK "" => OK false |
||
| 5704 | | OK _ => OK true |
||
| 5705 | | ERROR e => ERROR e |
||
| 5706 | |||
| 5707 | 70:2d3e1d1f99c0 | Chris | structure X = SubXml |
| 5708 | |||
| 5709 | fun svn_info context libname route = |
||
| 5710 | (* SVN 1.9 has info --show-item which is just what we need, |
||
| 5711 | but at this point we still have 1.8 on the CI boxes so we |
||
| 5712 | might as well aim to support it. For that we really have to |
||
| 5713 | use the XML output format, since the default info output is |
||
| 5714 | localised. This is the only thing our mini-XML parser is |
||
| 5715 | used for though, so it would be good to trim it at some |
||
| 5716 | point *) |
||
| 5717 | let fun find elt [] = OK elt |
||
| 5718 | | find { children, ... } (first :: rest) =
|
||
| 5719 | case List.find (fn (X.ELEMENT { name, ... }) => name = first
|
||
| 5720 | | _ => false) |
||
| 5721 | children of |
||
| 5722 | NONE => ERROR ("No element \"" ^ first ^ "\" in SVN XML")
|
||
| 5723 | | SOME (X.ELEMENT e) => find e rest |
||
| 5724 | | SOME _ => ERROR "Internal error" |
||
| 5725 | in |
||
| 5726 | case svn_command_output context libname ["info", "--xml"] of |
||
| 5727 | ERROR e => ERROR e |
||
| 5728 | | OK xml => |
||
| 5729 | case X.parse xml of |
||
| 5730 | X.ERROR e => ERROR e |
||
| 5731 | | X.OK (X.DOCUMENT doc) => find doc route |
||
| 5732 | end |
||
| 5733 | |||
| 5734 | fun exists context libname = |
||
| 5735 | OK (OS.FileSys.isDir (FileBits.subpath context libname ".svn")) |
||
| 5736 | handle _ => OK false |
||
| 5737 | |||
| 5738 | fun remote_for context (libname, source) = |
||
| 5739 | Provider.remote_url context SVN source libname |
||
| 5740 | |||
| 5741 | (* Remote the checkout came from, not necessarily the one we want *) |
||
| 5742 | fun actual_remote_for context libname = |
||
| 5743 | case svn_info context libname ["entry", "url"] of |
||
| 5744 | ERROR e => ERROR e |
||
| 5745 | | OK { children, ... } =>
|
||
| 5746 | case List.find (fn (X.TEXT _) => true | _ => false) children of |
||
| 5747 | NONE => ERROR "No content for URL in SVN info XML" |
||
| 5748 | | SOME (X.TEXT url) => OK url |
||
| 5749 | | SOME _ => ERROR "Internal error" |
||
| 5750 | |||
| 5751 | fun id_of context libname = |
||
| 5752 | case svn_info context libname ["entry"] of |
||
| 5753 | ERROR e => ERROR e |
||
| 5754 | | OK { children, ... } =>
|
||
| 5755 | case List.find |
||
| 5756 | (fn (X.ATTRIBUTE { name = "revision", ... }) => true
|
||
| 5757 | | _ => false) |
||
| 5758 | children of |
||
| 5759 | NONE => ERROR "No revision for entry in SVN info XML" |
||
| 5760 | | SOME (X.ATTRIBUTE { value, ... }) => OK value
|
||
| 5761 | | SOME _ => ERROR "Internal error" |
||
| 5762 | |||
| 5763 | fun is_at context (libname, id_or_tag) = |
||
| 5764 | case id_of context libname of |
||
| 5765 | ERROR e => ERROR e |
||
| 5766 | | OK id => OK (id = id_or_tag) |
||
| 5767 | |||
| 5768 | fun is_on_branch context (libname, b) = |
||
| 5769 | OK (b = DEFAULT_BRANCH) |
||
| 5770 | |||
| 5771 | fun check_remote context (libname, source) = |
||
| 5772 | case (remote_for context (libname, source), |
||
| 5773 | actual_remote_for context libname) of |
||
| 5774 | (_, ERROR e) => ERROR e |
||
| 5775 | | (url, OK actual) => |
||
| 5776 | if actual = url |
||
| 5777 | then OK () |
||
| 5778 | else svn_command context libname ["relocate", url] |
||
| 5779 | |||
| 5780 | fun is_newest context (libname, source, branch) = |
||
| 5781 | case check_remote context (libname, source) of |
||
| 5782 | ERROR e => ERROR e |
||
| 5783 | | OK () => |
||
| 5784 | case svn_command_lines context libname |
||
| 5785 | ["status", "--show-updates"] of |
||
| 5786 | ERROR e => ERROR e |
||
| 5787 | | OK lines => |
||
| 5788 | case rev lines of |
||
| 5789 | [] => ERROR "No result returned for server status" |
||
| 5790 | | last_line::_ => |
||
| 5791 | case rev (String.tokens (fn c => c = #" ") last_line) of |
||
| 5792 | [] => ERROR "No revision field found in server status" |
||
| 5793 | | server_id::_ => is_at context (libname, server_id) |
||
| 5794 | |||
| 5795 | fun is_newest_locally context (libname, branch) = |
||
| 5796 | OK true (* no local history *) |
||
| 5797 | |||
| 5798 | fun is_modified_locally context libname = |
||
| 5799 | case svn_command_output context libname ["status"] of |
||
| 5800 | ERROR e => ERROR e |
||
| 5801 | | OK "" => OK false |
||
| 5802 | | OK _ => OK true |
||
| 5803 | |||
| 5804 | fun checkout context (libname, source, branch) = |
||
| 5805 | let val url = remote_for context (libname, source) |
||
| 5806 | val path = FileBits.libpath context libname |
||
| 5807 | in |
||
| 5808 | if FileBits.nonempty_dir_exists path |
||
| 5809 | then (* Surprisingly, SVN itself has no problem with |
||
| 5810 | this. But for consistency with other VCSes we |
||
| 5811 | don't allow it *) |
||
| 5812 | ERROR ("Refusing checkout to nonempty dir \"" ^ path ^ "\"")
|
||
| 5813 | else |
||
| 5814 | (* make the lib dir rather than just the ext dir, since |
||
| 5815 | the lib dir might be nested and svn will happily check |
||
| 5816 | out into an existing empty dir anyway *) |
||
| 5817 | case FileBits.mkpath (FileBits.libpath context libname) of |
||
| 5818 | ERROR e => ERROR e |
||
| 5819 | | _ => svn_command context "" ["checkout", url, libname] |
||
| 5820 | end |
||
| 5821 | |||
| 5822 | fun update context (libname, source, branch) = |
||
| 5823 | case check_remote context (libname, source) of |
||
| 5824 | ERROR e => ERROR e |
||
| 5825 | | OK () => |
||
| 5826 | case svn_command context libname |
||
| 5827 | ["update", "--accept", "postpone"] of |
||
| 5828 | ERROR e => ERROR e |
||
| 5829 | | _ => OK () |
||
| 5830 | |||
| 5831 | fun update_to context (libname, _, "") = |
||
| 5832 | ERROR "Non-empty id (tag or revision id) required for update_to" |
||
| 5833 | | update_to context (libname, source, id) = |
||
| 5834 | case check_remote context (libname, source) of |
||
| 5835 | ERROR e => ERROR e |
||
| 5836 | | OK () => |
||
| 5837 | case svn_command context libname |
||
| 5838 | ["update", "-r", id, "--accept", "postpone"] of |
||
| 5839 | ERROR e => ERROR e |
||
| 5840 | | OK _ => OK () |
||
| 5841 | |||
| 5842 | fun copy_url_for context libname = |
||
| 5843 | actual_remote_for context libname |
||
| 5844 | |||
| 5845 | end |
||
| 5846 | |||
| 5847 | structure AnyLibControl :> LIB_CONTROL = struct |
||
| 5848 | |||
| 5849 | structure H = LibControlFn(HgControl) |
||
| 5850 | structure G = LibControlFn(GitControl) |
||
| 5851 | structure S = LibControlFn(SvnControl) |
||
| 5852 | |||
| 5853 | fun review context (spec as { vcs, ... } : libspec) =
|
||
| 5854 | (fn HG => H.review | GIT => G.review | SVN => S.review) vcs context spec |
||
| 5855 | |||
| 5856 | fun status context (spec as { vcs, ... } : libspec) =
|
||
| 5857 | (fn HG => H.status | GIT => G.status | SVN => S.status) vcs context spec |
||
| 5858 | |||
| 5859 | fun update context (spec as { vcs, ... } : libspec) =
|
||
| 5860 | (fn HG => H.update | GIT => G.update | SVN => S.update) vcs context spec |
||
| 5861 | |||
| 5862 | fun id_of context (spec as { vcs, ... } : libspec) =
|
||
| 5863 | (fn HG => H.id_of | GIT => G.id_of | SVN => S.id_of) vcs context spec |
||
| 5864 | |||
| 5865 | 76:a6c9a0ca493e | Chris | fun is_working context vcs = |
| 5866 | (fn HG => H.is_working | GIT => G.is_working | SVN => S.is_working) |
||
| 5867 | vcs context vcs |
||
| 5868 | |||
| 5869 | 70:2d3e1d1f99c0 | Chris | end |
| 5870 | |||
| 5871 | |||
| 5872 | type exclusions = string list |
||
| 5873 | |||
| 5874 | structure Archive :> sig |
||
| 5875 | |||
| 5876 | val archive : string * exclusions -> project -> OS.Process.status |
||
| 5877 | |||
| 5878 | end = struct |
||
| 5879 | |||
| 5880 | (* The idea of "archive" is to replace hg/git archive, which won't |
||
| 5881 | 76:a6c9a0ca493e | Chris | include files, like the Repoint-introduced external libraries, |
| 5882 | 70:2d3e1d1f99c0 | Chris | that are not under version control with the main repo. |
| 5883 | |||
| 5884 | The process goes like this: |
||
| 5885 | |||
| 5886 | - Make sure we have a target filename from the user, and take |
||
| 5887 | its basename as our archive directory name |
||
| 5888 | |||
| 5889 | - Make an "archive root" subdir of the project repo, named |
||
| 5890 | 76:a6c9a0ca493e | Chris | typically .repoint-archive |
| 5891 | 70:2d3e1d1f99c0 | Chris | |
| 5892 | - Identify the VCS used for the project repo. Note that any |
||
| 5893 | explicit references to VCS type in this structure are to |
||
| 5894 | 76:a6c9a0ca493e | Chris | the VCS used for the project (something Repoint doesn't |
| 5895 | 70:2d3e1d1f99c0 | Chris | otherwise care about), not for an individual library |
| 5896 | |||
| 5897 | 76:a6c9a0ca493e | Chris | - Synthesise a Repoint project with the archive root as its |
| 5898 | 70:2d3e1d1f99c0 | Chris | root path, "." as its extdir, with one library whose |
| 5899 | name is the user-supplied basename and whose explicit |
||
| 5900 | source URL is the original project root; update that |
||
| 5901 | project -- thus cloning the original project to a subdir |
||
| 5902 | of the archive root |
||
| 5903 | |||
| 5904 | 76:a6c9a0ca493e | Chris | - Synthesise a Repoint project identical to the original one for |
| 5905 | 70:2d3e1d1f99c0 | Chris | this project, but with the newly-cloned copy as its root |
| 5906 | path; update that project -- thus checking out clean copies |
||
| 5907 | of the external library dirs |
||
| 5908 | |||
| 5909 | - Call out to an archive program to archive up the new copy, |
||
| 5910 | running e.g. |
||
| 5911 | tar cvzf project-release.tar.gz \ |
||
| 5912 | --exclude=.hg --exclude=.git project-release |
||
| 5913 | in the archive root dir |
||
| 5914 | |||
| 5915 | 76:a6c9a0ca493e | Chris | - (We also omit the repoint-project.json file and any trace of |
| 5916 | Repoint. It can't properly be run in a directory where the |
||
| 5917 | 70:2d3e1d1f99c0 | Chris | external project folders already exist but their repo history |
| 5918 | 76:a6c9a0ca493e | Chris | does not. End users shouldn't get to see Repoint) |
| 5919 | 70:2d3e1d1f99c0 | Chris | |
| 5920 | - Clean up by deleting the new copy |
||
| 5921 | *) |
||
| 5922 | |||
| 5923 | fun project_vcs_id_and_url dir = |
||
| 5924 | let val context = {
|
||
| 5925 | rootpath = dir, |
||
| 5926 | extdir = ".", |
||
| 5927 | providers = [], |
||
| 5928 | accounts = [] |
||
| 5929 | } |
||
| 5930 | val vcs_maybe = |
||
| 5931 | case [HgControl.exists context ".", |
||
| 5932 | GitControl.exists context ".", |
||
| 5933 | SvnControl.exists context "."] of |
||
| 5934 | [OK true, OK false, OK false] => OK HG |
||
| 5935 | | [OK false, OK true, OK false] => OK GIT |
||
| 5936 | | [OK false, OK false, OK true] => OK SVN |
||
| 5937 | | _ => ERROR ("Unable to identify VCS for directory " ^ dir)
|
||
| 5938 | in |
||
| 5939 | case vcs_maybe of |
||
| 5940 | ERROR e => ERROR e |
||
| 5941 | | OK vcs => |
||
| 5942 | case (fn HG => HgControl.id_of |
||
| 5943 | | GIT => GitControl.id_of |
||
| 5944 | | SVN => SvnControl.id_of) |
||
| 5945 | vcs context "." of |
||
| 5946 | ERROR e => ERROR ("Unable to find id of project repo: " ^ e)
|
||
| 5947 | | OK id => |
||
| 5948 | case (fn HG => HgControl.copy_url_for |
||
| 5949 | | GIT => GitControl.copy_url_for |
||
| 5950 | | SVN => SvnControl.copy_url_for) |
||
| 5951 | vcs context "." of |
||
| 5952 | ERROR e => ERROR ("Unable to find URL of project repo: "
|
||
| 5953 | ^ e) |
||
| 5954 | | OK url => OK (vcs, id, url) |
||
| 5955 | end |
||
| 5956 | |||
| 5957 | fun make_archive_root (context : context) = |
||
| 5958 | let val path = OS.Path.joinDirFile {
|
||
| 5959 | dir = #rootpath context, |
||
| 5960 | 76:a6c9a0ca493e | Chris | file = RepointFilenames.archive_dir |
| 5961 | 70:2d3e1d1f99c0 | Chris | } |
| 5962 | in |
||
| 5963 | case FileBits.mkpath path of |
||
| 5964 | ERROR e => raise Fail ("Failed to create archive directory \""
|
||
| 5965 | ^ path ^ "\": " ^ e) |
||
| 5966 | | OK () => path |
||
| 5967 | end |
||
| 5968 | |||
| 5969 | fun archive_path archive_dir target_name = |
||
| 5970 | OS.Path.joinDirFile {
|
||
| 5971 | dir = archive_dir, |
||
| 5972 | file = target_name |
||
| 5973 | } |
||
| 5974 | |||
| 5975 | fun check_nonexistent path = |
||
| 5976 | case SOME (OS.FileSys.fileSize path) handle OS.SysErr _ => NONE of |
||
| 5977 | NONE => () |
||
| 5978 | | _ => raise Fail ("Path " ^ path ^ " exists, not overwriting")
|
||
| 5979 | |||
| 5980 | fun make_archive_copy target_name (vcs, project_id, source_url) |
||
| 5981 | ({ context, ... } : project) =
|
||
| 5982 | let val archive_root = make_archive_root context |
||
| 5983 | val synthetic_context = {
|
||
| 5984 | rootpath = archive_root, |
||
| 5985 | extdir = ".", |
||
| 5986 | providers = [], |
||
| 5987 | accounts = [] |
||
| 5988 | } |
||
| 5989 | val synthetic_library = {
|
||
| 5990 | libname = target_name, |
||
| 5991 | vcs = vcs, |
||
| 5992 | source = URL_SOURCE source_url, |
||
| 5993 | branch = DEFAULT_BRANCH, (* overridden by pinned id below *) |
||
| 5994 | project_pin = PINNED project_id, |
||
| 5995 | lock_pin = PINNED project_id |
||
| 5996 | } |
||
| 5997 | val path = archive_path archive_root target_name |
||
| 5998 | val _ = print ("Cloning original project to " ^ path
|
||
| 5999 | ^ " at revision " ^ project_id ^ "...\n"); |
||
| 6000 | val _ = check_nonexistent path |
||
| 6001 | in |
||
| 6002 | case AnyLibControl.update synthetic_context synthetic_library of |
||
| 6003 | ERROR e => ERROR ("Failed to clone original project to "
|
||
| 6004 | ^ path ^ ": " ^ e) |
||
| 6005 | | OK _ => OK archive_root |
||
| 6006 | end |
||
| 6007 | |||
| 6008 | fun update_archive archive_root target_name |
||
| 6009 | (project as { context, ... } : project) =
|
||
| 6010 | let val synthetic_context = {
|
||
| 6011 | rootpath = archive_path archive_root target_name, |
||
| 6012 | extdir = #extdir context, |
||
| 6013 | providers = #providers context, |
||
| 6014 | accounts = #accounts context |
||
| 6015 | } |
||
| 6016 | in |
||
| 6017 | foldl (fn (lib, acc) => |
||
| 6018 | case acc of |
||
| 6019 | ERROR e => ERROR e |
||
| 6020 | | OK () => AnyLibControl.update synthetic_context lib) |
||
| 6021 | (OK ()) |
||
| 6022 | (#libs project) |
||
| 6023 | end |
||
| 6024 | |||
| 6025 | datatype packer = TAR |
||
| 6026 | | TAR_GZ |
||
| 6027 | | TAR_BZ2 |
||
| 6028 | | TAR_XZ |
||
| 6029 | (* could add other packers, e.g. zip, if we knew how to |
||
| 6030 | handle the file omissions etc properly in pack_archive *) |
||
| 6031 | |||
| 6032 | fun packer_and_basename path = |
||
| 6033 | let val extensions = [ (".tar", TAR),
|
||
| 6034 | (".tar.gz", TAR_GZ),
|
||
| 6035 | (".tar.bz2", TAR_BZ2),
|
||
| 6036 | (".tar.xz", TAR_XZ)]
|
||
| 6037 | val filename = OS.Path.file path |
||
| 6038 | in |
||
| 6039 | foldl (fn ((ext, packer), acc) => |
||
| 6040 | if String.isSuffix ext filename |
||
| 6041 | then SOME (packer, |
||
| 6042 | String.substring (filename, 0, |
||
| 6043 | String.size filename - |
||
| 6044 | String.size ext)) |
||
| 6045 | else acc) |
||
| 6046 | NONE |
||
| 6047 | extensions |
||
| 6048 | end |
||
| 6049 | |||
| 6050 | fun pack_archive archive_root target_name target_path packer exclusions = |
||
| 6051 | case FileBits.command {
|
||
| 6052 | rootpath = archive_root, |
||
| 6053 | extdir = ".", |
||
| 6054 | providers = [], |
||
| 6055 | accounts = [] |
||
| 6056 | } "" ([ |
||
| 6057 | "tar", |
||
| 6058 | case packer of |
||
| 6059 | TAR => "cf" |
||
| 6060 | | TAR_GZ => "czf" |
||
| 6061 | | TAR_BZ2 => "cjf" |
||
| 6062 | | TAR_XZ => "cJf", |
||
| 6063 | target_path, |
||
| 6064 | "--exclude=.hg", |
||
| 6065 | "--exclude=.git", |
||
| 6066 | "--exclude=.svn", |
||
| 6067 | 76:a6c9a0ca493e | Chris | "--exclude=repoint", |
| 6068 | "--exclude=repoint.sml", |
||
| 6069 | "--exclude=repoint.ps1", |
||
| 6070 | "--exclude=repoint.bat", |
||
| 6071 | "--exclude=repoint-project.json", |
||
| 6072 | "--exclude=repoint-lock.json" |
||
| 6073 | 70:2d3e1d1f99c0 | Chris | ] @ (map (fn e => "--exclude=" ^ e) exclusions) @ |
| 6074 | [ target_name ]) |
||
| 6075 | of |
||
| 6076 | ERROR e => ERROR e |
||
| 6077 | | OK _ => FileBits.rmpath (archive_path archive_root target_name) |
||
| 6078 | |||
| 6079 | fun archive (target_path, exclusions) (project : project) = |
||
| 6080 | let val _ = check_nonexistent target_path |
||
| 6081 | val (packer, name) = |
||
| 6082 | case packer_and_basename target_path of |
||
| 6083 | NONE => raise Fail ("Unsupported archive file extension in "
|
||
| 6084 | ^ target_path) |
||
| 6085 | | SOME pn => pn |
||
| 6086 | val details = |
||
| 6087 | case project_vcs_id_and_url (#rootpath (#context project)) of |
||
| 6088 | ERROR e => raise Fail e |
||
| 6089 | | OK details => details |
||
| 6090 | val archive_root = |
||
| 6091 | case make_archive_copy name details project of |
||
| 6092 | ERROR e => raise Fail e |
||
| 6093 | | OK archive_root => archive_root |
||
| 6094 | val outcome = |
||
| 6095 | case update_archive archive_root name project of |
||
| 6096 | ERROR e => ERROR e |
||
| 6097 | | OK _ => |
||
| 6098 | case pack_archive archive_root name |
||
| 6099 | target_path packer exclusions of |
||
| 6100 | ERROR e => ERROR e |
||
| 6101 | | OK _ => OK () |
||
| 6102 | in |
||
| 6103 | case outcome of |
||
| 6104 | ERROR e => raise Fail e |
||
| 6105 | | OK () => OS.Process.success |
||
| 6106 | end |
||
| 6107 | |||
| 6108 | end |
||
| 6109 | |||
| 6110 | val libobjname = "libraries" |
||
| 6111 | |||
| 6112 | fun load_libspec spec_json lock_json libname : libspec = |
||
| 6113 | let open JsonBits |
||
| 6114 | val libobj = lookup_mandatory spec_json [libobjname, libname] |
||
| 6115 | val vcs = lookup_mandatory_string libobj ["vcs"] |
||
| 6116 | val retrieve = lookup_optional_string libobj |
||
| 6117 | val service = retrieve ["service"] |
||
| 6118 | val owner = retrieve ["owner"] |
||
| 6119 | val repo = retrieve ["repository"] |
||
| 6120 | val url = retrieve ["url"] |
||
| 6121 | val branch = retrieve ["branch"] |
||
| 6122 | val project_pin = case retrieve ["pin"] of |
||
| 6123 | NONE => UNPINNED |
||
| 6124 | | SOME p => PINNED p |
||
| 6125 | val lock_pin = case lookup_optional lock_json [libobjname, libname] of |
||
| 6126 | NONE => UNPINNED |
||
| 6127 | | SOME ll => case lookup_optional_string ll ["pin"] of |
||
| 6128 | SOME p => PINNED p |
||
| 6129 | | NONE => UNPINNED |
||
| 6130 | in |
||
| 6131 | {
|
||
| 6132 | libname = libname, |
||
| 6133 | vcs = case vcs of |
||
| 6134 | "hg" => HG |
||
| 6135 | | "git" => GIT |
||
| 6136 | | "svn" => SVN |
||
| 6137 | | other => raise Fail ("Unknown version-control system \"" ^
|
||
| 6138 | other ^ "\""), |
||
| 6139 | source = case (url, service, owner, repo) of |
||
| 6140 | (SOME u, NONE, _, _) => URL_SOURCE u |
||
| 6141 | | (NONE, SOME ss, owner, repo) => |
||
| 6142 | SERVICE_SOURCE { service = ss, owner = owner, repo = repo }
|
||
| 6143 | | _ => raise Fail ("Must have exactly one of service " ^
|
||
| 6144 | "or url string"), |
||
| 6145 | project_pin = project_pin, |
||
| 6146 | lock_pin = lock_pin, |
||
| 6147 | branch = case branch of |
||
| 6148 | NONE => DEFAULT_BRANCH |
||
| 6149 | | SOME b => |
||
| 6150 | case vcs of |
||
| 6151 | "svn" => raise Fail ("Branches not supported for " ^
|
||
| 6152 | "svn repositories; change " ^ |
||
| 6153 | "URL instead") |
||
| 6154 | | _ => BRANCH b |
||
| 6155 | } |
||
| 6156 | end |
||
| 6157 | |||
| 6158 | fun load_userconfig () : userconfig = |
||
| 6159 | let val home = FileBits.homedir () |
||
| 6160 | val conf_json = |
||
| 6161 | JsonBits.load_json_from |
||
| 6162 | (OS.Path.joinDirFile {
|
||
| 6163 | dir = home, |
||
| 6164 | 76:a6c9a0ca493e | Chris | file = RepointFilenames.user_config_file }) |
| 6165 | 70:2d3e1d1f99c0 | Chris | handle IO.Io _ => Json.OBJECT [] |
| 6166 | in |
||
| 6167 | {
|
||
| 6168 | accounts = case JsonBits.lookup_optional conf_json ["accounts"] of |
||
| 6169 | NONE => [] |
||
| 6170 | | SOME (Json.OBJECT aa) => |
||
| 6171 | map (fn (k, (Json.STRING v)) => |
||
| 6172 | { service = k, login = v }
|
||
| 6173 | | _ => raise Fail |
||
| 6174 | "String expected for account name") |
||
| 6175 | aa |
||
| 6176 | | _ => raise Fail "Array expected for accounts", |
||
| 6177 | providers = Provider.load_providers conf_json |
||
| 6178 | } |
||
| 6179 | end |
||
| 6180 | |||
| 6181 | datatype pintype = |
||
| 6182 | NO_LOCKFILE | |
||
| 6183 | USE_LOCKFILE |
||
| 6184 | |||
| 6185 | fun load_project (userconfig : userconfig) rootpath pintype : project = |
||
| 6186 | let val spec_file = FileBits.project_spec_path rootpath |
||
| 6187 | val lock_file = FileBits.project_lock_path rootpath |
||
| 6188 | val _ = if OS.FileSys.access (spec_file, [OS.FileSys.A_READ]) |
||
| 6189 | handle OS.SysErr _ => false |
||
| 6190 | then () |
||
| 6191 | else raise Fail ("Failed to open project spec file " ^
|
||
| 6192 | 76:a6c9a0ca493e | Chris | (RepointFilenames.project_file) ^ " in " ^ |
| 6193 | 70:2d3e1d1f99c0 | Chris | rootpath ^ |
| 6194 | ".\nPlease ensure the spec file is in the " ^ |
||
| 6195 | "project root and run this from there.") |
||
| 6196 | val spec_json = JsonBits.load_json_from spec_file |
||
| 6197 | val lock_json = if pintype = USE_LOCKFILE |
||
| 6198 | then JsonBits.load_json_from lock_file |
||
| 6199 | handle IO.Io _ => Json.OBJECT [] |
||
| 6200 | else Json.OBJECT [] |
||
| 6201 | val extdir = JsonBits.lookup_mandatory_string spec_json |
||
| 6202 | ["config", "extdir"] |
||
| 6203 | val spec_libs = JsonBits.lookup_optional spec_json [libobjname] |
||
| 6204 | val lock_libs = JsonBits.lookup_optional lock_json [libobjname] |
||
| 6205 | val providers = Provider.load_more_providers |
||
| 6206 | (#providers userconfig) spec_json |
||
| 6207 | val libnames = case spec_libs of |
||
| 6208 | NONE => [] |
||
| 6209 | | SOME (Json.OBJECT ll) => map (fn (k, v) => k) ll |
||
| 6210 | | _ => raise Fail "Object expected for libs" |
||
| 6211 | in |
||
| 6212 | {
|
||
| 6213 | context = {
|
||
| 6214 | rootpath = rootpath, |
||
| 6215 | extdir = extdir, |
||
| 6216 | providers = providers, |
||
| 6217 | accounts = #accounts userconfig |
||
| 6218 | }, |
||
| 6219 | libs = map (load_libspec spec_json lock_json) libnames |
||
| 6220 | } |
||
| 6221 | end |
||
| 6222 | |||
| 6223 | fun save_lock_file rootpath locks = |
||
| 6224 | let val lock_file = FileBits.project_lock_path rootpath |
||
| 6225 | open Json |
||
| 6226 | val lock_json = |
||
| 6227 | OBJECT [ |
||
| 6228 | (libobjname, |
||
| 6229 | OBJECT (map (fn { libname, id_or_tag } =>
|
||
| 6230 | (libname, |
||
| 6231 | OBJECT [ ("pin", STRING id_or_tag) ]))
|
||
| 6232 | locks)) |
||
| 6233 | ] |
||
| 6234 | in |
||
| 6235 | JsonBits.save_json_to lock_file lock_json |
||
| 6236 | end |
||
| 6237 | |||
| 6238 | fun pad_to n str = |
||
| 6239 | if n <= String.size str then str |
||
| 6240 | else pad_to n (str ^ " ") |
||
| 6241 | |||
| 6242 | fun hline_to 0 = "" |
||
| 6243 | | hline_to n = "-" ^ hline_to (n-1) |
||
| 6244 | |||
| 6245 | val libname_width = 28 |
||
| 6246 | val libstate_width = 11 |
||
| 6247 | val localstate_width = 17 |
||
| 6248 | val notes_width = 5 |
||
| 6249 | val divider = " | " |
||
| 6250 | val clear_line = "\r" ^ pad_to 80 ""; |
||
| 6251 | |||
| 6252 | fun print_status_header () = |
||
| 6253 | print (clear_line ^ "\n " ^ |
||
| 6254 | pad_to libname_width "Library" ^ divider ^ |
||
| 6255 | pad_to libstate_width "State" ^ divider ^ |
||
| 6256 | pad_to localstate_width "Local" ^ divider ^ |
||
| 6257 | "Notes" ^ "\n " ^ |
||
| 6258 | hline_to libname_width ^ "-+-" ^ |
||
| 6259 | hline_to libstate_width ^ "-+-" ^ |
||
| 6260 | hline_to localstate_width ^ "-+-" ^ |
||
| 6261 | hline_to notes_width ^ "\n") |
||
| 6262 | |||
| 6263 | fun print_outcome_header () = |
||
| 6264 | print (clear_line ^ "\n " ^ |
||
| 6265 | pad_to libname_width "Library" ^ divider ^ |
||
| 6266 | pad_to libstate_width "Outcome" ^ divider ^ |
||
| 6267 | "Notes" ^ "\n " ^ |
||
| 6268 | hline_to libname_width ^ "-+-" ^ |
||
| 6269 | hline_to libstate_width ^ "-+-" ^ |
||
| 6270 | hline_to notes_width ^ "\n") |
||
| 6271 | |||
| 6272 | 76:a6c9a0ca493e | Chris | fun print_status with_network (lib : libspec, status) = |
| 6273 | 70:2d3e1d1f99c0 | Chris | let val libstate_str = |
| 6274 | case status of |
||
| 6275 | OK (ABSENT, _) => "Absent" |
||
| 6276 | | OK (CORRECT, _) => if with_network then "Correct" else "Present" |
||
| 6277 | | OK (SUPERSEDED, _) => "Superseded" |
||
| 6278 | | OK (WRONG, _) => "Wrong" |
||
| 6279 | | ERROR _ => "Error" |
||
| 6280 | val localstate_str = |
||
| 6281 | case status of |
||
| 6282 | OK (_, MODIFIED) => "Modified" |
||
| 6283 | | OK (_, LOCK_MISMATCHED) => "Differs from Lock" |
||
| 6284 | | OK (_, CLEAN) => "Clean" |
||
| 6285 | | ERROR _ => "" |
||
| 6286 | val error_str = |
||
| 6287 | case status of |
||
| 6288 | ERROR e => e |
||
| 6289 | | _ => "" |
||
| 6290 | in |
||
| 6291 | print (" " ^
|
||
| 6292 | 76:a6c9a0ca493e | Chris | pad_to libname_width (#libname lib) ^ divider ^ |
| 6293 | 70:2d3e1d1f99c0 | Chris | pad_to libstate_width libstate_str ^ divider ^ |
| 6294 | pad_to localstate_width localstate_str ^ divider ^ |
||
| 6295 | error_str ^ "\n") |
||
| 6296 | end |
||
| 6297 | |||
| 6298 | 76:a6c9a0ca493e | Chris | fun print_update_outcome (lib : libspec, outcome) = |
| 6299 | 70:2d3e1d1f99c0 | Chris | let val outcome_str = |
| 6300 | case outcome of |
||
| 6301 | OK id => "Ok" |
||
| 6302 | | ERROR e => "Failed" |
||
| 6303 | val error_str = |
||
| 6304 | case outcome of |
||
| 6305 | ERROR e => e |
||
| 6306 | | _ => "" |
||
| 6307 | in |
||
| 6308 | print (" " ^
|
||
| 6309 | 76:a6c9a0ca493e | Chris | pad_to libname_width (#libname lib) ^ divider ^ |
| 6310 | 70:2d3e1d1f99c0 | Chris | pad_to libstate_width outcome_str ^ divider ^ |
| 6311 | error_str ^ "\n") |
||
| 6312 | end |
||
| 6313 | |||
| 6314 | 76:a6c9a0ca493e | Chris | fun vcs_name HG = ("Mercurial", "hg")
|
| 6315 | | vcs_name GIT = ("Git", "git")
|
||
| 6316 | | vcs_name SVN = ("Subversion", "svn")
|
||
| 6317 | |||
| 6318 | fun print_problem_summary context lines = |
||
| 6319 | let val failed_vcs = |
||
| 6320 | foldl (fn (({ vcs, ... } : libspec, ERROR _), acc) => vcs::acc
|
||
| 6321 | | (_, acc) => acc) [] lines |
||
| 6322 | fun report_nonworking vcs error = |
||
| 6323 | print ((if error = "" then "" else error ^ "\n\n") ^ |
||
| 6324 | "Error: The project uses the " ^ (#1 (vcs_name vcs)) ^ |
||
| 6325 | " version control system, but its\n" ^ |
||
| 6326 | "executable program (" ^ (#2 (vcs_name vcs)) ^
|
||
| 6327 | ") does not appear to be installed in the program path\n\n") |
||
| 6328 | fun check_working [] checked = () |
||
| 6329 | | check_working (vcs::rest) checked = |
||
| 6330 | if List.exists (fn v => vcs = v) checked |
||
| 6331 | then check_working rest checked |
||
| 6332 | else |
||
| 6333 | case AnyLibControl.is_working context vcs of |
||
| 6334 | OK true => check_working rest checked |
||
| 6335 | | OK false => (report_nonworking vcs ""; |
||
| 6336 | check_working rest (vcs::checked)) |
||
| 6337 | | ERROR e => (report_nonworking vcs e; |
||
| 6338 | check_working rest (vcs::checked)) |
||
| 6339 | in |
||
| 6340 | print "\nError: Some operations failed\n\n"; |
||
| 6341 | check_working failed_vcs [] |
||
| 6342 | end |
||
| 6343 | |||
| 6344 | fun act_and_print action print_header print_line context (libs : libspec list) = |
||
| 6345 | let val lines = map (fn lib => (lib, action lib)) libs |
||
| 6346 | val imperfect = List.exists (fn (_, ERROR _) => true | _ => false) lines |
||
| 6347 | 70:2d3e1d1f99c0 | Chris | val _ = print_header () |
| 6348 | in |
||
| 6349 | app print_line lines; |
||
| 6350 | 76:a6c9a0ca493e | Chris | if imperfect then print_problem_summary context lines else (); |
| 6351 | 70:2d3e1d1f99c0 | Chris | lines |
| 6352 | end |
||
| 6353 | |||
| 6354 | fun return_code_for outcomes = |
||
| 6355 | foldl (fn ((_, result), acc) => |
||
| 6356 | case result of |
||
| 6357 | ERROR _ => OS.Process.failure |
||
| 6358 | | _ => acc) |
||
| 6359 | OS.Process.success |
||
| 6360 | outcomes |
||
| 6361 | |||
| 6362 | fun status_of_project ({ context, libs } : project) =
|
||
| 6363 | return_code_for (act_and_print (AnyLibControl.status context) |
||
| 6364 | print_status_header (print_status false) |
||
| 6365 | 76:a6c9a0ca493e | Chris | context libs) |
| 6366 | 70:2d3e1d1f99c0 | Chris | |
| 6367 | fun review_project ({ context, libs } : project) =
|
||
| 6368 | return_code_for (act_and_print (AnyLibControl.review context) |
||
| 6369 | print_status_header (print_status true) |
||
| 6370 | 76:a6c9a0ca493e | Chris | context libs) |
| 6371 | 70:2d3e1d1f99c0 | Chris | |
| 6372 | fun lock_project ({ context, libs } : project) =
|
||
| 6373 | let val _ = if FileBits.verbose () |
||
| 6374 | then print ("Scanning IDs for lock file...\n")
|
||
| 6375 | else () |
||
| 6376 | 76:a6c9a0ca493e | Chris | val outcomes = map (fn lib => (lib, AnyLibControl.id_of context lib)) |
| 6377 | 70:2d3e1d1f99c0 | Chris | libs |
| 6378 | val locks = |
||
| 6379 | List.concat |
||
| 6380 | 76:a6c9a0ca493e | Chris | (map (fn (lib : libspec, result) => |
| 6381 | 70:2d3e1d1f99c0 | Chris | case result of |
| 6382 | ERROR _ => [] |
||
| 6383 | 76:a6c9a0ca493e | Chris | | OK id => [{ libname = #libname lib,
|
| 6384 | id_or_tag = id }]) |
||
| 6385 | 70:2d3e1d1f99c0 | Chris | outcomes) |
| 6386 | val return_code = return_code_for outcomes |
||
| 6387 | val _ = print clear_line |
||
| 6388 | in |
||
| 6389 | if OS.Process.isSuccess return_code |
||
| 6390 | then save_lock_file (#rootpath context) locks |
||
| 6391 | else (); |
||
| 6392 | return_code |
||
| 6393 | end |
||
| 6394 | |||
| 6395 | fun update_project (project as { context, libs }) =
|
||
| 6396 | let val outcomes = act_and_print |
||
| 6397 | (AnyLibControl.update context) |
||
| 6398 | 76:a6c9a0ca493e | Chris | print_outcome_header print_update_outcome |
| 6399 | context libs |
||
| 6400 | 70:2d3e1d1f99c0 | Chris | val _ = if List.exists (fn (_, OK _) => true | _ => false) outcomes |
| 6401 | then lock_project project |
||
| 6402 | else OS.Process.success |
||
| 6403 | in |
||
| 6404 | return_code_for outcomes |
||
| 6405 | end |
||
| 6406 | |||
| 6407 | fun load_local_project pintype = |
||
| 6408 | let val userconfig = load_userconfig () |
||
| 6409 | val rootpath = OS.FileSys.getDir () |
||
| 6410 | in |
||
| 6411 | load_project userconfig rootpath pintype |
||
| 6412 | end |
||
| 6413 | |||
| 6414 | fun with_local_project pintype f = |
||
| 6415 | 76:a6c9a0ca493e | Chris | let open OS.Process |
| 6416 | val return_code = |
||
| 6417 | f (load_local_project pintype) |
||
| 6418 | handle Fail msg => |
||
| 6419 | failure before print ("Error: " ^ msg)
|
||
| 6420 | | JsonBits.Config msg => |
||
| 6421 | failure before print ("Error in configuration: " ^ msg)
|
||
| 6422 | | e => |
||
| 6423 | failure before print ("Error: " ^ exnMessage e)
|
||
| 6424 | 70:2d3e1d1f99c0 | Chris | val _ = print "\n"; |
| 6425 | in |
||
| 6426 | return_code |
||
| 6427 | end |
||
| 6428 | |||
| 6429 | fun review () = with_local_project USE_LOCKFILE review_project |
||
| 6430 | fun status () = with_local_project USE_LOCKFILE status_of_project |
||
| 6431 | fun update () = with_local_project NO_LOCKFILE update_project |
||
| 6432 | fun lock () = with_local_project NO_LOCKFILE lock_project |
||
| 6433 | fun install () = with_local_project USE_LOCKFILE update_project |
||
| 6434 | |||
| 6435 | fun version () = |
||
| 6436 | 76:a6c9a0ca493e | Chris | (print ("v" ^ repoint_version ^ "\n");
|
| 6437 | 70:2d3e1d1f99c0 | Chris | OS.Process.success) |
| 6438 | |||
| 6439 | fun usage () = |
||
| 6440 | 76:a6c9a0ca493e | Chris | (print "\nRepoint "; |
| 6441 | 70:2d3e1d1f99c0 | Chris | version (); |
| 6442 | print ("\nA simple manager for third-party source code dependencies.\n\n"
|
||
| 6443 | ^ "Usage:\n\n" |
||
| 6444 | 76:a6c9a0ca493e | Chris | ^ " repoint <command>\n\n" |
| 6445 | 70:2d3e1d1f99c0 | Chris | ^ "where <command> is one of:\n\n" |
| 6446 | ^ " status print quick report on local status only, without using network\n" |
||
| 6447 | ^ " review check configured libraries against their providers, and report\n" |
||
| 6448 | ^ " install update configured libraries according to project specs and lock file\n" |
||
| 6449 | ^ " update update configured libraries and lock file according to project specs\n" |
||
| 6450 | ^ " lock update lock file to match local library status\n" |
||
| 6451 | ^ " archive pack up project and all libraries into an archive file\n" |
||
| 6452 | 76:a6c9a0ca493e | Chris | ^ " (invoke as 'repoint archive target-file.tar.gz')\n" |
| 6453 | ^ " version print the Repoint version number and exit\n\n"); |
||
| 6454 | 70:2d3e1d1f99c0 | Chris | OS.Process.failure) |
| 6455 | |||
| 6456 | fun archive target args = |
||
| 6457 | case args of |
||
| 6458 | [] => |
||
| 6459 | with_local_project USE_LOCKFILE (Archive.archive (target, [])) |
||
| 6460 | | "--exclude"::xs => |
||
| 6461 | with_local_project USE_LOCKFILE (Archive.archive (target, xs)) |
||
| 6462 | | _ => usage () |
||
| 6463 | |||
| 6464 | 76:a6c9a0ca493e | Chris | fun repoint args = |
| 6465 | 70:2d3e1d1f99c0 | Chris | let val return_code = |
| 6466 | case args of |
||
| 6467 | ["review"] => review () |
||
| 6468 | | ["status"] => status () |
||
| 6469 | | ["install"] => install () |
||
| 6470 | | ["update"] => update () |
||
| 6471 | | ["lock"] => lock () |
||
| 6472 | | ["version"] => version () |
||
| 6473 | | "archive"::target::args => archive target args |
||
| 6474 | 76:a6c9a0ca493e | Chris | | arg::_ => (print ("Error: unknown argument \"" ^ arg ^ "\"\n");
|
| 6475 | usage ()) |
||
| 6476 | 70:2d3e1d1f99c0 | Chris | | _ => usage () |
| 6477 | in |
||
| 6478 | OS.Process.exit return_code; |
||
| 6479 | () |
||
| 6480 | end |
||
| 6481 | |||
| 6482 | fun main () = |
||
| 6483 | 76:a6c9a0ca493e | Chris | repoint (CommandLine.arguments ()) |
| 6484 | 0:f89128a316e7 | cannam | /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ |
| 6485 | |||
| 6486 | /* |
||
| 6487 | Vamp Plugin Tester |
||
| 6488 | Chris Cannam, cannam@all-day-breakfast.com |
||
| 6489 | Centre for Digital Music, Queen Mary, University of London. |
||
| 6490 | 59:6732dd5e7139 | Chris | Copyright 2009-2015 QMUL. |
| 6491 | 0:f89128a316e7 | cannam | |
| 6492 | This program loads a Vamp plugin and tests its susceptibility to a |
||
| 6493 | number of common pitfalls, including handling of extremes of input |
||
| 6494 | data. If you can think of any additional useful tests that are |
||
| 6495 | easily added, please send them to me. |
||
| 6496 | |||
| 6497 | Permission is hereby granted, free of charge, to any person |
||
| 6498 | obtaining a copy of this software and associated documentation |
||
| 6499 | files (the "Software"), to deal in the Software without |
||
| 6500 | restriction, including without limitation the rights to use, copy, |
||
| 6501 | modify, merge, publish, distribute, sublicense, and/or sell copies |
||
| 6502 | of the Software, and to permit persons to whom the Software is |
||
| 6503 | furnished to do so, subject to the following conditions: |
||
| 6504 | |||
| 6505 | The above copyright notice and this permission notice shall be |
||
| 6506 | included in all copies or substantial portions of the Software. |
||
| 6507 | |||
| 6508 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
||
| 6509 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
||
| 6510 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
||
| 6511 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR |
||
| 6512 | ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF |
||
| 6513 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
||
| 6514 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
||
| 6515 | |||
| 6516 | Except as contained in this notice, the names of the Centre for |
||
| 6517 | Digital Music; Queen Mary, University of London; and Chris Cannam |
||
| 6518 | shall not be used in advertising or otherwise to promote the sale, |
||
| 6519 | use or other dealings in this Software without prior written |
||
| 6520 | authorization. |
||
| 6521 | */ |
||
| 6522 | |||
| 6523 | #include <vamp-hostsdk/PluginHostAdapter.h> |
||
| 6524 | #include <vamp-hostsdk/PluginInputDomainAdapter.h> |
||
| 6525 | #include <vamp-hostsdk/PluginLoader.h> |
||
| 6526 | |||
| 6527 | #include <iostream> |
||
| 6528 | |||
| 6529 | #include <cstdlib> |
||
| 6530 | 8:3019cb6b538d | cannam | #include <cstring> |
| 6531 | 0:f89128a316e7 | cannam | |
| 6532 | #include "Tester.h" |
||
| 6533 | |||
| 6534 | using namespace std; |
||
| 6535 | |||
| 6536 | 40:649f32c7eb41 | Chris | static const std::string VERSION="1.1"; |
| 6537 | |||
| 6538 | 0:f89128a316e7 | cannam | void usage(const char *name) |
| 6539 | {
|
||
| 6540 | cerr << "\n" |
||
| 6541 | 9:7cc55187f5db | cannam | << name << ": A Vamp plugin host that tests plugins for common errors.\n" |
| 6542 | 0:f89128a316e7 | cannam | "Chris Cannam, Centre for Digital Music, Queen Mary, University of London.\n" |
| 6543 | 59:6732dd5e7139 | Chris | "Copyright 2009-2015 QMUL.\n" |
| 6544 | 0:f89128a316e7 | cannam | "Freely redistributable; published under a BSD-style license.\n\n" |
| 6545 | "Usage:\n" |
||
| 6546 | 40:649f32c7eb41 | Chris | " " << name << " [-nv] [-t <test>] <pluginbasename>:<plugin>\n" |
| 6547 | " " << name << " [-nv] [-t <test>] -a\n" |
||
| 6548 | " " << name << " -l\n\n" |
||
| 6549 | 0:f89128a316e7 | cannam | "Example:\n" |
| 6550 | " " << name << " vamp-example-plugins:amplitudefollower\n\n" |
||
| 6551 | 25:612333efd521 | cannam | "Options:\n" |
| 6552 | " -a, --all Test all plugins found in Vamp path\n\n" |
||
| 6553 | " -n, --nondeterministic Plugins may be nondeterministic: print a note\n" |
||
| 6554 | " instead of an error if results differ between runs\n\n" |
||
| 6555 | " -v, --verbose Show returned features each time a note, warning,\n" |
||
| 6556 | 40:649f32c7eb41 | Chris | " or error arises from feature data\n\n" |
| 6557 | " -t, --test <test> Run only a single test, not the full test suite.\n" |
||
| 6558 | " Identify the test by its id, e.g. A3\n\n" |
||
| 6559 | " -l, --list-tests List tests by id and name\n\n" |
||
| 6560 | " --version Display the version of " << name << "\n" |
||
| 6561 | 8:3019cb6b538d | cannam | "\nIf you have access to a runtime memory checker, you may find it especially\n" |
| 6562 | 0:f89128a316e7 | cannam | "helpful to run this tester under it and watch for errors thus provoked.\n" |
| 6563 | << endl; |
||
| 6564 | exit(2); |
||
| 6565 | } |
||
| 6566 | |||
| 6567 | int main(int argc, char **argv) |
||
| 6568 | {
|
||
| 6569 | char *scooter = argv[0]; |
||
| 6570 | char *name = 0; |
||
| 6571 | while (scooter && *scooter) {
|
||
| 6572 | if (*scooter == '/' || *scooter == '\\') name = ++scooter; |
||
| 6573 | else ++scooter; |
||
| 6574 | } |
||
| 6575 | if (!name || !*name) name = argv[0]; |
||
| 6576 | 8:3019cb6b538d | cannam | |
| 6577 | bool nondeterministic = false; |
||
| 6578 | bool verbose = false; |
||
| 6579 | 25:612333efd521 | cannam | bool all = false; |
| 6580 | 40:649f32c7eb41 | Chris | bool list = false; |
| 6581 | string plugin; |
||
| 6582 | string single; |
||
| 6583 | |||
| 6584 | // Would be better to use getopt, but let's avoid the dependency for now |
||
| 6585 | 8:3019cb6b538d | cannam | for (int i = 1; i < argc; ++i) {
|
| 6586 | if (!argv[i]) break; |
||
| 6587 | if (argv[i][0] == '-') {
|
||
| 6588 | 40:649f32c7eb41 | Chris | if (!strcmp(argv[i], "-vn") || |
| 6589 | !strcmp(argv[i], "-nv")) {
|
||
| 6590 | verbose = true; |
||
| 6591 | nondeterministic = true; |
||
| 6592 | continue; |
||
| 6593 | } |
||
| 6594 | 8:3019cb6b538d | cannam | if (!strcmp(argv[i], "-v") || |
| 6595 | !strcmp(argv[i], "--verbose")) {
|
||
| 6596 | 25:612333efd521 | cannam | verbose = true; |
| 6597 | 8:3019cb6b538d | cannam | continue; |
| 6598 | } |
||
| 6599 | if (!strcmp(argv[i], "-n") || |
||
| 6600 | !strcmp(argv[i], "--nondeterministic")) {
|
||
| 6601 | 25:612333efd521 | cannam | nondeterministic = true; |
| 6602 | continue; |
||
| 6603 | } |
||
| 6604 | if (!strcmp(argv[i], "-a") || |
||
| 6605 | !strcmp(argv[i], "--all")) {
|
||
| 6606 | all = true; |
||
| 6607 | 8:3019cb6b538d | cannam | continue; |
| 6608 | } |
||
| 6609 | 40:649f32c7eb41 | Chris | if (!strcmp(argv[i], "-l") || |
| 6610 | !strcmp(argv[i], "--list-tests")) {
|
||
| 6611 | list = true; |
||
| 6612 | continue; |
||
| 6613 | } |
||
| 6614 | if (!strcmp(argv[i], "-t") || |
||
| 6615 | !strcmp(argv[i], "--test")) {
|
||
| 6616 | if (i + 1 < argc) {
|
||
| 6617 | single = argv[i+1]; |
||
| 6618 | ++i; |
||
| 6619 | } else {
|
||
| 6620 | usage(name); |
||
| 6621 | } |
||
| 6622 | continue; |
||
| 6623 | } |
||
| 6624 | if (!strcmp(argv[i], "--version")) {
|
||
| 6625 | cout << "v" << VERSION << endl; |
||
| 6626 | return 0; |
||
| 6627 | } |
||
| 6628 | 8:3019cb6b538d | cannam | usage(name); |
| 6629 | } else {
|
||
| 6630 | 40:649f32c7eb41 | Chris | if (plugin != "") usage(name); |
| 6631 | else plugin = argv[i]; |
||
| 6632 | 8:3019cb6b538d | cannam | } |
| 6633 | } |
||
| 6634 | 40:649f32c7eb41 | Chris | |
| 6635 | if (list) {
|
||
| 6636 | if (all || nondeterministic || (single != "") || (plugin != "")) {
|
||
| 6637 | usage(name); |
||
| 6638 | } |
||
| 6639 | Tester::listTests(); |
||
| 6640 | return 0; |
||
| 6641 | } |
||
| 6642 | 0:f89128a316e7 | cannam | |
| 6643 | 40:649f32c7eb41 | Chris | if (plugin == "" && !all) usage(name); |
| 6644 | if (plugin != "" && all) usage(name); |
||
| 6645 | 25:612333efd521 | cannam | |
| 6646 | 0:f89128a316e7 | cannam | cerr << name << ": Running..." << endl; |
| 6647 | |||
| 6648 | 8:3019cb6b538d | cannam | Test::Options opts = Test::NoOption; |
| 6649 | if (nondeterministic) opts |= Test::NonDeterministic; |
||
| 6650 | if (verbose) opts |= Test::Verbose; |
||
| 6651 | 39:07144cdcbedf | Chris | if (single != "") opts |= Test::SingleTest; |
| 6652 | 8:3019cb6b538d | cannam | |
| 6653 | 25:612333efd521 | cannam | if (all) {
|
| 6654 | 0:f89128a316e7 | cannam | bool good = true; |
| 6655 | Vamp::HostExt::PluginLoader::PluginKeyList keys = |
||
| 6656 | Vamp::HostExt::PluginLoader::getInstance()->listPlugins(); |
||
| 6657 | 33:005ac7a3d827 | Chris | if (keys.size() == 0) {
|
| 6658 | cout << name << ": NOTE: No plugins found!" << endl; |
||
| 6659 | cout << name << ": (No libraries in search path, or no descriptors in library)" << endl; |
||
| 6660 | 37:a835f9cf3a3d | Chris | return 2; |
| 6661 | 33:005ac7a3d827 | Chris | } |
| 6662 | 4:d8724c5a6d83 | cannam | int notes = 0, warnings = 0, errors = 0; |
| 6663 | 0:f89128a316e7 | cannam | for (int i = 0; i < (int)keys.size(); ++i) {
|
| 6664 | 3:0f65bb22172b | cannam | cout << "Testing plugin: " << keys[i] << endl; |
| 6665 | 39:07144cdcbedf | Chris | Tester tester(keys[i], opts, single); |
| 6666 | 4:d8724c5a6d83 | cannam | if (tester.test(notes, warnings, errors)) {
|
| 6667 | 3:0f65bb22172b | cannam | cout << name << ": All tests succeeded for this plugin" << endl; |
| 6668 | 0:f89128a316e7 | cannam | } else {
|
| 6669 | 3:0f65bb22172b | cannam | cout << name << ": Some tests failed for this plugin" << endl; |
| 6670 | 0:f89128a316e7 | cannam | good = false; |
| 6671 | } |
||
| 6672 | 3:0f65bb22172b | cannam | cout << endl; |
| 6673 | 0:f89128a316e7 | cannam | } |
| 6674 | if (good) {
|
||
| 6675 | 4:d8724c5a6d83 | cannam | cout << name << ": All tests succeeded"; |
| 6676 | if (warnings > 0) {
|
||
| 6677 | cout << ", with " << warnings << " warning(s)"; |
||
| 6678 | if (notes > 0) {
|
||
| 6679 | cout << " and " << notes << " other note(s)"; |
||
| 6680 | } |
||
| 6681 | } else if (notes > 0) {
|
||
| 6682 | cout << ", with " << notes << " note(s)"; |
||
| 6683 | } |
||
| 6684 | cout << endl; |
||
| 6685 | 0:f89128a316e7 | cannam | return 0; |
| 6686 | } else {
|
||
| 6687 | 3:0f65bb22172b | cannam | cout << name << ": Some tests failed" << endl; |
| 6688 | 0:f89128a316e7 | cannam | return 1; |
| 6689 | } |
||
| 6690 | } else {
|
||
| 6691 | 40:649f32c7eb41 | Chris | Tester tester(plugin, opts, single); |
| 6692 | 4:d8724c5a6d83 | cannam | int notes = 0, warnings = 0, errors = 0; |
| 6693 | if (tester.test(notes, warnings, errors)) {
|
||
| 6694 | cout << name << ": All tests succeeded"; |
||
| 6695 | if (warnings > 0) {
|
||
| 6696 | cout << ", with " << warnings << " warning(s)"; |
||
| 6697 | if (notes > 0) {
|
||
| 6698 | cout << " and " << notes << " other note(s)"; |
||
| 6699 | } |
||
| 6700 | } else if (notes > 0) {
|
||
| 6701 | cout << ", with " << notes << " note(s)"; |
||
| 6702 | } |
||
| 6703 | cout << endl; |
||
| 6704 | 0:f89128a316e7 | cannam | return 0; |
| 6705 | } else {
|
||
| 6706 | 3:0f65bb22172b | cannam | cout << name << ": Some tests failed" << endl; |
| 6707 | 0:f89128a316e7 | cannam | return 1; |
| 6708 | } |
||
| 6709 | } |
||
| 6710 | } |