To check out this repository please hg clone the following URL, or open the URL using EasyMercurial or your preferred Mercurial client.

Statistics Download as Zip
| Branch: | Tag: | Revision:

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 &notes, 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 &notes, 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 &notes, 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 &registry();
3129 39:07144cdcbedf Chris
3130
    bool performTest(std::string id, int &notes, 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
}