You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucy.apache.org by nw...@apache.org on 2013/02/03 22:50:38 UTC

[lucy-commits] [2/2] git commit: refs/heads/cfc-tests-rc1 - LUCY-201 Port CFC tests to C

Updated Branches:
  refs/heads/cfc-tests-rc1 [created] 9898c8645


LUCY-201 Port CFC tests to C

Port the Clownfish compiler tests from Perl to C.

Implement a new class CFCTest used to collect results from test runs and
print formatted results in multiple formats: "tap" for TAP output and
"clownfish" for a generic Clownfish format.

All the tests checking exceptions thrown with CFCUtil_die haven't been
ported yet. We first need a way to catch exceptions from C.


Project: http://git-wip-us.apache.org/repos/asf/lucy/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucy/commit/9898c864
Tree: http://git-wip-us.apache.org/repos/asf/lucy/tree/9898c864
Diff: http://git-wip-us.apache.org/repos/asf/lucy/diff/9898c864

Branch: refs/heads/cfc-tests-rc1
Commit: 9898c8645b5d94c057626c578fe18fa353f0f4a4
Parents: 3a0c80d
Author: Nick Wellnhofer <we...@aevum.de>
Authored: Wed Jan 23 22:56:33 2013 +0100
Committer: Nick Wellnhofer <we...@aevum.de>
Committed: Sun Feb 3 22:40:31 2013 +0100

----------------------------------------------------------------------
 clownfish/compiler/include/CFC.h                  |    1 +
 clownfish/compiler/perl/lib/Clownfish/CFC.pm      |   19 +
 clownfish/compiler/perl/lib/Clownfish/CFC.xs      |   20 +
 clownfish/compiler/perl/lib/Clownfish/CFC/Test.pm |   20 +
 clownfish/compiler/perl/t/core/001-util.t         |   25 +
 clownfish/compiler/perl/t/core/050-docucomment.t  |   25 +
 clownfish/compiler/perl/t/core/051-symbol.t       |   25 +
 clownfish/compiler/perl/t/core/052-version.t      |   25 +
 clownfish/compiler/perl/t/core/100-type.t         |   25 +
 clownfish/compiler/perl/t/core/200-function.t     |   25 +
 clownfish/compiler/perl/t/core/201-method.t       |   25 +
 clownfish/compiler/perl/t/core/300-variable.t     |   25 +
 clownfish/compiler/perl/t/core/301-param_list.t   |   25 +
 clownfish/compiler/perl/t/core/400-file_spec.t    |   25 +
 clownfish/compiler/perl/t/core/401-class.t        |   25 +
 clownfish/compiler/perl/t/core/402-c_block.t      |   25 +
 clownfish/compiler/perl/t/core/403-parcel.t       |   25 +
 clownfish/compiler/perl/t/core/404-file.t         |   25 +
 clownfish/compiler/perl/t/core/500-hierarchy.t    |   25 +
 clownfish/compiler/perl/t/core/600-parser.t       |   25 +
 clownfish/compiler/perl/typemap                   |    1 +
 clownfish/compiler/src/CFCTest.c                  |  490 +++++++++++++++
 clownfish/compiler/src/CFCTest.h                  |  174 ++++++
 clownfish/compiler/src/CFCTestCBlock.c            |   60 ++
 clownfish/compiler/src/CFCTestClass.c             |  357 +++++++++++
 clownfish/compiler/src/CFCTestDocuComment.c       |  107 ++++
 clownfish/compiler/src/CFCTestFile.c              |  132 ++++
 clownfish/compiler/src/CFCTestFileSpec.c          |   53 ++
 clownfish/compiler/src/CFCTestFunction.c          |   76 +++
 clownfish/compiler/src/CFCTestHierarchy.c         |  231 +++++++
 clownfish/compiler/src/CFCTestMethod.c            |  251 ++++++++
 clownfish/compiler/src/CFCTestParamList.c         |   77 +++
 clownfish/compiler/src/CFCTestParcel.c            |   94 +++
 clownfish/compiler/src/CFCTestParser.c            |  312 ++++++++++
 clownfish/compiler/src/CFCTestSymbol.c            |  144 +++++
 clownfish/compiler/src/CFCTestType.c              |  517 ++++++++++++++++
 clownfish/compiler/src/CFCTestUtil.c              |  120 ++++
 clownfish/compiler/src/CFCTestVariable.c          |  100 +++
 clownfish/compiler/src/CFCTestVersion.c           |   69 ++
 39 files changed, 3825 insertions(+), 0 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucy/blob/9898c864/clownfish/compiler/include/CFC.h
----------------------------------------------------------------------
diff --git a/clownfish/compiler/include/CFC.h b/clownfish/compiler/include/CFC.h
index 93f530e..643263e 100644
--- a/clownfish/compiler/include/CFC.h
+++ b/clownfish/compiler/include/CFC.h
@@ -29,6 +29,7 @@
 #include "CFCParcel.h"
 #include "CFCParser.h"
 #include "CFCSymbol.h"
+#include "CFCTest.h"
 #include "CFCType.h"
 #include "CFCUtil.h"
 #include "CFCVariable.h"

http://git-wip-us.apache.org/repos/asf/lucy/blob/9898c864/clownfish/compiler/perl/lib/Clownfish/CFC.pm
----------------------------------------------------------------------
diff --git a/clownfish/compiler/perl/lib/Clownfish/CFC.pm b/clownfish/compiler/perl/lib/Clownfish/CFC.pm
index 29487be..6635096 100644
--- a/clownfish/compiler/perl/lib/Clownfish/CFC.pm
+++ b/clownfish/compiler/perl/lib/Clownfish/CFC.pm
@@ -849,6 +849,25 @@ BEGIN { XSLoader::load( 'Clownfish::CFC', '0.01' ) }
     }
 }
 
+{
+    package Clownfish::CFC::Test;
+    BEGIN { push our @ISA, 'Clownfish::CFC::Base' }
+    use Clownfish::CFC::Util qw( verify_args );
+    use Carp;
+
+    my %new_PARAMS = (
+        formatter_name => 'tap',
+    );
+
+    sub new {
+        my ( $either, %args ) = @_;
+        verify_args( \%new_PARAMS, %args ) or confess $@;
+        confess "no subclassing allowed" unless $either eq __PACKAGE__;
+        $args{formatter_name} = 'tap' unless defined $args{formatter_name};
+        return _new( @args{qw( formatter_name )} );
+    }
+}
+
 1;
 
 =head1 NAME

http://git-wip-us.apache.org/repos/asf/lucy/blob/9898c864/clownfish/compiler/perl/lib/Clownfish/CFC.xs
----------------------------------------------------------------------
diff --git a/clownfish/compiler/perl/lib/Clownfish/CFC.xs b/clownfish/compiler/perl/lib/Clownfish/CFC.xs
index 415338e..13ee4d4 100644
--- a/clownfish/compiler/perl/lib/Clownfish/CFC.xs
+++ b/clownfish/compiler/perl/lib/Clownfish/CFC.xs
@@ -2389,3 +2389,23 @@ CODE:
     RETVAL = S_cfcbase_to_perlref((CFCBase*)parcel);
 OUTPUT: RETVAL
 
+
+MODULE = Clownfish::CFC   PACKAGE = Clownfish::CFC::Test
+
+SV*
+_new(formatter_name)
+    const char *formatter_name;
+CODE:
+    CFCTest *self = CFCTest_new(formatter_name);
+    RETVAL = S_cfcbase_to_perlref(self);
+    CFCBase_decref((CFCBase*)self);
+OUTPUT: RETVAL
+
+int
+run_batch(self, klass)
+    CFCTest *self;
+    const char *klass;
+CODE:
+    RETVAL = CFCTest_run_batch(self, klass);
+OUTPUT: RETVAL
+

http://git-wip-us.apache.org/repos/asf/lucy/blob/9898c864/clownfish/compiler/perl/lib/Clownfish/CFC/Test.pm
----------------------------------------------------------------------
diff --git a/clownfish/compiler/perl/lib/Clownfish/CFC/Test.pm b/clownfish/compiler/perl/lib/Clownfish/CFC/Test.pm
new file mode 100644
index 0000000..bebd673
--- /dev/null
+++ b/clownfish/compiler/perl/lib/Clownfish/CFC/Test.pm
@@ -0,0 +1,20 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+package Clownfish::CFC::Test;
+use Clownfish::CFC;
+
+1;
+

http://git-wip-us.apache.org/repos/asf/lucy/blob/9898c864/clownfish/compiler/perl/t/core/001-util.t
----------------------------------------------------------------------
diff --git a/clownfish/compiler/perl/t/core/001-util.t b/clownfish/compiler/perl/t/core/001-util.t
new file mode 100644
index 0000000..b3ad862
--- /dev/null
+++ b/clownfish/compiler/perl/t/core/001-util.t
@@ -0,0 +1,25 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+use strict;
+use warnings;
+
+use Clownfish::CFC::Test;
+
+my $test   = Clownfish::CFC::Test->new;
+my $passed = $test->run_batch('Clownfish::CFC::Util');
+
+exit($passed ? 0 : 1);
+

http://git-wip-us.apache.org/repos/asf/lucy/blob/9898c864/clownfish/compiler/perl/t/core/050-docucomment.t
----------------------------------------------------------------------
diff --git a/clownfish/compiler/perl/t/core/050-docucomment.t b/clownfish/compiler/perl/t/core/050-docucomment.t
new file mode 100644
index 0000000..09ad252
--- /dev/null
+++ b/clownfish/compiler/perl/t/core/050-docucomment.t
@@ -0,0 +1,25 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+use strict;
+use warnings;
+
+use Clownfish::CFC::Test;
+
+my $test   = Clownfish::CFC::Test->new;
+my $passed = $test->run_batch('Clownfish::CFC::Model::DocuComment');
+
+exit($passed ? 0 : 1);
+

http://git-wip-us.apache.org/repos/asf/lucy/blob/9898c864/clownfish/compiler/perl/t/core/051-symbol.t
----------------------------------------------------------------------
diff --git a/clownfish/compiler/perl/t/core/051-symbol.t b/clownfish/compiler/perl/t/core/051-symbol.t
new file mode 100644
index 0000000..4209292
--- /dev/null
+++ b/clownfish/compiler/perl/t/core/051-symbol.t
@@ -0,0 +1,25 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+use strict;
+use warnings;
+
+use Clownfish::CFC::Test;
+
+my $test   = Clownfish::CFC::Test->new;
+my $passed = $test->run_batch('Clownfish::CFC::Model::Symbol');
+
+exit($passed ? 0 : 1);
+

http://git-wip-us.apache.org/repos/asf/lucy/blob/9898c864/clownfish/compiler/perl/t/core/052-version.t
----------------------------------------------------------------------
diff --git a/clownfish/compiler/perl/t/core/052-version.t b/clownfish/compiler/perl/t/core/052-version.t
new file mode 100644
index 0000000..d5c2f63
--- /dev/null
+++ b/clownfish/compiler/perl/t/core/052-version.t
@@ -0,0 +1,25 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+use strict;
+use warnings;
+
+use Clownfish::CFC::Test;
+
+my $test   = Clownfish::CFC::Test->new;
+my $passed = $test->run_batch('Clownfish::CFC::Model::Version');
+
+exit($passed ? 0 : 1);
+

http://git-wip-us.apache.org/repos/asf/lucy/blob/9898c864/clownfish/compiler/perl/t/core/100-type.t
----------------------------------------------------------------------
diff --git a/clownfish/compiler/perl/t/core/100-type.t b/clownfish/compiler/perl/t/core/100-type.t
new file mode 100644
index 0000000..f5a0e24
--- /dev/null
+++ b/clownfish/compiler/perl/t/core/100-type.t
@@ -0,0 +1,25 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+use strict;
+use warnings;
+
+use Clownfish::CFC::Test;
+
+my $test   = Clownfish::CFC::Test->new;
+my $passed = $test->run_batch('Clownfish::CFC::Model::Type');
+
+exit($passed ? 0 : 1);
+

http://git-wip-us.apache.org/repos/asf/lucy/blob/9898c864/clownfish/compiler/perl/t/core/200-function.t
----------------------------------------------------------------------
diff --git a/clownfish/compiler/perl/t/core/200-function.t b/clownfish/compiler/perl/t/core/200-function.t
new file mode 100644
index 0000000..d70d2b8
--- /dev/null
+++ b/clownfish/compiler/perl/t/core/200-function.t
@@ -0,0 +1,25 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+use strict;
+use warnings;
+
+use Clownfish::CFC::Test;
+
+my $test   = Clownfish::CFC::Test->new;
+my $passed = $test->run_batch('Clownfish::CFC::Model::Function');
+
+exit($passed ? 0 : 1);
+

http://git-wip-us.apache.org/repos/asf/lucy/blob/9898c864/clownfish/compiler/perl/t/core/201-method.t
----------------------------------------------------------------------
diff --git a/clownfish/compiler/perl/t/core/201-method.t b/clownfish/compiler/perl/t/core/201-method.t
new file mode 100644
index 0000000..0f24c25
--- /dev/null
+++ b/clownfish/compiler/perl/t/core/201-method.t
@@ -0,0 +1,25 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+use strict;
+use warnings;
+
+use Clownfish::CFC::Test;
+
+my $test   = Clownfish::CFC::Test->new;
+my $passed = $test->run_batch('Clownfish::CFC::Model::Method');
+
+exit($passed ? 0 : 1);
+

http://git-wip-us.apache.org/repos/asf/lucy/blob/9898c864/clownfish/compiler/perl/t/core/300-variable.t
----------------------------------------------------------------------
diff --git a/clownfish/compiler/perl/t/core/300-variable.t b/clownfish/compiler/perl/t/core/300-variable.t
new file mode 100644
index 0000000..c89f830
--- /dev/null
+++ b/clownfish/compiler/perl/t/core/300-variable.t
@@ -0,0 +1,25 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+use strict;
+use warnings;
+
+use Clownfish::CFC::Test;
+
+my $test   = Clownfish::CFC::Test->new;
+my $passed = $test->run_batch('Clownfish::CFC::Model::Variable');
+
+exit($passed ? 0 : 1);
+

http://git-wip-us.apache.org/repos/asf/lucy/blob/9898c864/clownfish/compiler/perl/t/core/301-param_list.t
----------------------------------------------------------------------
diff --git a/clownfish/compiler/perl/t/core/301-param_list.t b/clownfish/compiler/perl/t/core/301-param_list.t
new file mode 100644
index 0000000..23cdeb6
--- /dev/null
+++ b/clownfish/compiler/perl/t/core/301-param_list.t
@@ -0,0 +1,25 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+use strict;
+use warnings;
+
+use Clownfish::CFC::Test;
+
+my $test   = Clownfish::CFC::Test->new;
+my $passed = $test->run_batch('Clownfish::CFC::Model::ParamList');
+
+exit($passed ? 0 : 1);
+

http://git-wip-us.apache.org/repos/asf/lucy/blob/9898c864/clownfish/compiler/perl/t/core/400-file_spec.t
----------------------------------------------------------------------
diff --git a/clownfish/compiler/perl/t/core/400-file_spec.t b/clownfish/compiler/perl/t/core/400-file_spec.t
new file mode 100644
index 0000000..c3d25b4
--- /dev/null
+++ b/clownfish/compiler/perl/t/core/400-file_spec.t
@@ -0,0 +1,25 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+use strict;
+use warnings;
+
+use Clownfish::CFC::Test;
+
+my $test   = Clownfish::CFC::Test->new;
+my $passed = $test->run_batch('Clownfish::CFC::Model::FileSpec');
+
+exit($passed ? 0 : 1);
+

http://git-wip-us.apache.org/repos/asf/lucy/blob/9898c864/clownfish/compiler/perl/t/core/401-class.t
----------------------------------------------------------------------
diff --git a/clownfish/compiler/perl/t/core/401-class.t b/clownfish/compiler/perl/t/core/401-class.t
new file mode 100644
index 0000000..6e3b1b2
--- /dev/null
+++ b/clownfish/compiler/perl/t/core/401-class.t
@@ -0,0 +1,25 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+use strict;
+use warnings;
+
+use Clownfish::CFC::Test;
+
+my $test   = Clownfish::CFC::Test->new;
+my $passed = $test->run_batch('Clownfish::CFC::Model::Class');
+
+exit($passed ? 0 : 1);
+

http://git-wip-us.apache.org/repos/asf/lucy/blob/9898c864/clownfish/compiler/perl/t/core/402-c_block.t
----------------------------------------------------------------------
diff --git a/clownfish/compiler/perl/t/core/402-c_block.t b/clownfish/compiler/perl/t/core/402-c_block.t
new file mode 100644
index 0000000..f993aa4
--- /dev/null
+++ b/clownfish/compiler/perl/t/core/402-c_block.t
@@ -0,0 +1,25 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+use strict;
+use warnings;
+
+use Clownfish::CFC::Test;
+
+my $test   = Clownfish::CFC::Test->new;
+my $passed = $test->run_batch('Clownfish::CFC::Model::CBlock');
+
+exit($passed ? 0 : 1);
+

http://git-wip-us.apache.org/repos/asf/lucy/blob/9898c864/clownfish/compiler/perl/t/core/403-parcel.t
----------------------------------------------------------------------
diff --git a/clownfish/compiler/perl/t/core/403-parcel.t b/clownfish/compiler/perl/t/core/403-parcel.t
new file mode 100644
index 0000000..1258f6d
--- /dev/null
+++ b/clownfish/compiler/perl/t/core/403-parcel.t
@@ -0,0 +1,25 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+use strict;
+use warnings;
+
+use Clownfish::CFC::Test;
+
+my $test   = Clownfish::CFC::Test->new;
+my $passed = $test->run_batch('Clownfish::CFC::Model::Parcel');
+
+exit($passed ? 0 : 1);
+

http://git-wip-us.apache.org/repos/asf/lucy/blob/9898c864/clownfish/compiler/perl/t/core/404-file.t
----------------------------------------------------------------------
diff --git a/clownfish/compiler/perl/t/core/404-file.t b/clownfish/compiler/perl/t/core/404-file.t
new file mode 100644
index 0000000..69c112e
--- /dev/null
+++ b/clownfish/compiler/perl/t/core/404-file.t
@@ -0,0 +1,25 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+use strict;
+use warnings;
+
+use Clownfish::CFC::Test;
+
+my $test   = Clownfish::CFC::Test->new;
+my $passed = $test->run_batch('Clownfish::CFC::Model::File');
+
+exit($passed ? 0 : 1);
+

http://git-wip-us.apache.org/repos/asf/lucy/blob/9898c864/clownfish/compiler/perl/t/core/500-hierarchy.t
----------------------------------------------------------------------
diff --git a/clownfish/compiler/perl/t/core/500-hierarchy.t b/clownfish/compiler/perl/t/core/500-hierarchy.t
new file mode 100644
index 0000000..5b60568
--- /dev/null
+++ b/clownfish/compiler/perl/t/core/500-hierarchy.t
@@ -0,0 +1,25 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+use strict;
+use warnings;
+
+use Clownfish::CFC::Test;
+
+my $test   = Clownfish::CFC::Test->new;
+my $passed = $test->run_batch('Clownfish::CFC::Model::Hierarchy');
+
+exit($passed ? 0 : 1);
+

http://git-wip-us.apache.org/repos/asf/lucy/blob/9898c864/clownfish/compiler/perl/t/core/600-parser.t
----------------------------------------------------------------------
diff --git a/clownfish/compiler/perl/t/core/600-parser.t b/clownfish/compiler/perl/t/core/600-parser.t
new file mode 100644
index 0000000..0311548
--- /dev/null
+++ b/clownfish/compiler/perl/t/core/600-parser.t
@@ -0,0 +1,25 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+use strict;
+use warnings;
+
+use Clownfish::CFC::Test;
+
+my $test   = Clownfish::CFC::Test->new;
+my $passed = $test->run_batch('Clownfish::CFC::Model::Parser');
+
+exit($passed ? 0 : 1);
+

http://git-wip-us.apache.org/repos/asf/lucy/blob/9898c864/clownfish/compiler/perl/typemap
----------------------------------------------------------------------
diff --git a/clownfish/compiler/perl/typemap b/clownfish/compiler/perl/typemap
index d746181..17be341 100644
--- a/clownfish/compiler/perl/typemap
+++ b/clownfish/compiler/perl/typemap
@@ -29,6 +29,7 @@ CFCParamList*	CLOWNFISH_MODEL
 CFCParcel*	CLOWNFISH_MODEL
 CFCParser*	CLOWNFISH_TYPE
 CFCSymbol*	CLOWNFISH_MODEL
+CFCTest*	CLOWNFISH_TYPE
 CFCType*	CLOWNFISH_MODEL
 CFCVariable*	CLOWNFISH_MODEL
 CFCVersion*	CLOWNFISH_MODEL

http://git-wip-us.apache.org/repos/asf/lucy/blob/9898c864/clownfish/compiler/src/CFCTest.c
----------------------------------------------------------------------
diff --git a/clownfish/compiler/src/CFCTest.c b/clownfish/compiler/src/CFCTest.c
new file mode 100644
index 0000000..30fc804
--- /dev/null
+++ b/clownfish/compiler/src/CFCTest.c
@@ -0,0 +1,490 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "charmony.h"
+
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#define CFC_NEED_BASE_STRUCT_DEF
+#define CFC_USE_TEST_MACROS
+#include "CFCTest.h"
+#include "CFCBase.h"
+#include "CFCParser.h"
+#include "CFCUtil.h"
+
+typedef struct CFCTestFormatter {
+    void (*batch_prologue)(const CFCTestBatch *batch);
+    void (*vtest_result)(int pass, int test_num, const char *fmt,
+                         va_list args);
+    void (*test_comment)(const char *fmt, ...);
+    void (*batch_comment)(const char *fmt, ...);
+    void (*summary)(const CFCTest *test);
+} CFCTestFormatter;
+
+struct CFCTest {
+    CFCBase base;
+    const CFCTestFormatter *formatter;
+    int num_tests;
+    int num_tests_failed;
+    int num_batches;
+    int num_batches_failed;
+    int num_tests_in_batch;
+    int num_failed_in_batch;
+};
+
+static int
+S_do_run_batch(CFCTest *self, const CFCTestBatch *batch);
+
+static void
+S_vtest_true(CFCTest *self, int cond, const char *fmt, va_list args);
+
+static void
+S_format_cfish_batch_prologue(const CFCTestBatch *batch);
+
+static void
+S_format_cfish_vtest_result(int pass, int test_num, const char *fmt,
+                            va_list args);
+
+static void
+S_format_cfish_test_comment(const char *fmt, ...);
+
+static void
+S_format_cfish_batch_comment(const char *fmt, ...);
+
+static void
+S_format_cfish_summary(const CFCTest *test);
+
+static void
+S_format_tap_batch_prologue(const CFCTestBatch *batch);
+
+static void
+S_format_tap_vtest_result(int pass, int test_num, const char *fmt,
+                          va_list args);
+
+static void
+S_format_tap_test_comment(const char *fmt, ...);
+
+static void
+S_format_tap_batch_comment(const char *fmt, ...);
+
+static void
+S_format_tap_summary(const CFCTest *test);
+
+static const CFCMeta CFCTEST_META = {
+    "Clownfish::CFC::Test",
+    sizeof(CFCTest),
+    (CFCBase_destroy_t)CFCTest_destroy
+};
+
+static const CFCTestFormatter S_formatter_cfish = {
+    S_format_cfish_batch_prologue,
+    S_format_cfish_vtest_result,
+    S_format_cfish_test_comment,
+    S_format_cfish_batch_comment,
+    S_format_cfish_summary
+};
+
+static const CFCTestFormatter S_formatter_tap = {
+    S_format_tap_batch_prologue,
+    S_format_tap_vtest_result,
+    S_format_tap_test_comment,
+    S_format_tap_batch_comment,
+    S_format_tap_summary
+};
+
+static const CFCTestBatch *const S_batches[] = {
+    &CFCTEST_BATCH_UTIL,
+    &CFCTEST_BATCH_DOCU_COMMENT,
+    &CFCTEST_BATCH_SYMBOL,
+    &CFCTEST_BATCH_VERSION,
+    &CFCTEST_BATCH_TYPE,
+    &CFCTEST_BATCH_FUNCTION,
+    &CFCTEST_BATCH_METHOD,
+    &CFCTEST_BATCH_VARIABLE,
+    &CFCTEST_BATCH_PARAM_LIST,
+    &CFCTEST_BATCH_FILE_SPEC,
+    &CFCTEST_BATCH_CLASS,
+    &CFCTEST_BATCH_C_BLOCK,
+    &CFCTEST_BATCH_PARCEL,
+    &CFCTEST_BATCH_FILE,
+    &CFCTEST_BATCH_HIERARCHY,
+    &CFCTEST_BATCH_PARSER,
+    NULL
+};
+
+CFCTest*
+CFCTest_new(const char *formatter_name) {
+    CFCTest *self = (CFCTest*)CFCBase_allocate(&CFCTEST_META);
+    return CFCTest_init(self, formatter_name);
+}
+
+CFCTest*
+CFCTest_init(CFCTest *self, const char *formatter_name) {
+    if (strcmp(formatter_name, "clownfish") == 0) {
+        self->formatter = &S_formatter_cfish;
+    }
+    else if (strcmp(formatter_name, "tap") == 0) {
+        self->formatter = &S_formatter_tap;
+    }
+    else {
+        CFCUtil_die("Unknown formatter name '%s'", formatter_name);
+    }
+
+    self->num_tests           = 0;
+    self->num_tests_failed    = 0;
+    self->num_batches         = 0;
+    self->num_batches_failed  = 0;
+    self->num_tests_in_batch  = 0;
+    self->num_failed_in_batch = 0;
+
+    return self;
+}
+
+void
+CFCTest_destroy(CFCTest *self) {
+    CFCBase_destroy((CFCBase*)self);
+}
+
+int
+CFCTest_run_all(CFCTest *self) {
+    int failed = 0;
+
+    for (int i = 0; S_batches[i]; ++i) {
+        if (!S_do_run_batch(self, S_batches[i])) { failed = 1; }
+    }
+
+    return !failed;
+}
+
+int
+CFCTest_run_batch(CFCTest *self, const char *name) {
+    for (int i = 0; S_batches[i]; ++i) {
+        const CFCTestBatch *batch = S_batches[i];
+        if (strcmp(batch->name, name) == 0) {
+            return S_do_run_batch(self, batch);
+        }
+    }
+
+    CFCUtil_die("Test batch '%s' not found", name);
+    return 0;
+}
+
+static int
+S_do_run_batch(CFCTest *self, const CFCTestBatch *batch) {
+    self->formatter->batch_prologue(batch);
+
+    batch->run(self);
+
+    int failed = 0;
+    void (*comment)(const char *fmt, ...) = self->formatter->batch_comment;
+
+    if (self->num_failed_in_batch > 0) {
+        failed = 1;
+        comment("%d/%d tests failed.\n", self->num_failed_in_batch,
+                self->num_tests_in_batch);
+    }
+    if (self->num_tests_in_batch != batch->num_planned) {
+        failed = 1;
+        comment("Bad plan: You planned %d tests but ran %d.\n",
+                batch->num_planned, self->num_tests_in_batch);
+    }
+
+    if (failed) {
+        self->num_batches_failed += 1;
+    }
+
+    self->num_batches += 1;
+    self->num_tests_in_batch  = 0;
+    self->num_failed_in_batch = 0;
+
+    return !failed;
+}
+
+void
+CFCTest_test_true(CFCTest *self, int cond, const char *fmt, ...) {
+    va_list args;
+    va_start(args, fmt);
+    S_vtest_true(self, cond, fmt, args);
+    va_end(args);
+}
+
+void
+CFCTest_test_string_equals(CFCTest *self, const char *result,
+                           const char *expected, const char *fmt, ...) {
+    int cond = (strcmp(result, expected) == 0);
+
+    va_list args;
+    va_start(args, fmt);
+    S_vtest_true(self, cond, fmt, args);
+    va_end(args);
+
+    if (!cond) {
+        self->formatter->test_comment("Expected '%s', got '%s'.\n",
+                                      expected, result);
+    }
+}
+
+void
+CFCTest_test_int_equals(CFCTest *self, long result, long expected,
+                        const char *fmt, ...) {
+    int cond = (result == expected);
+
+    va_list args;
+    va_start(args, fmt);
+    S_vtest_true(self, cond, fmt, args);
+    va_end(args);
+
+    if (!cond) {
+        self->formatter->test_comment("Expected '%ld', got '%ld'.\n",
+                                      expected, result);
+    }
+}
+
+void
+CFCTest_skip(CFCTest *self, int num) {
+    self->num_tests          += num;
+    self->num_tests_in_batch += num;
+}
+
+int
+CFCTest_finish(CFCTest *self) {
+    self->formatter->summary(self);
+
+    return self->num_batches != 0 && self->num_batches_failed == 0;
+}
+
+static void
+S_vtest_true(CFCTest *self, int cond, const char *fmt, va_list args) {
+    self->num_tests          += 1;
+    self->num_tests_in_batch += 1;
+
+    if (!cond) {
+        self->num_tests_failed    += 1;
+        self->num_failed_in_batch += 1;
+    }
+
+    self->formatter->vtest_result(cond, self->num_tests_in_batch, fmt, args);
+}
+
+static void
+S_format_cfish_batch_prologue(const CFCTestBatch *batch) {
+    printf("Testing %s...\n", batch->name);
+}
+
+static void
+S_format_cfish_vtest_result(int pass, int test_num, const char *fmt,
+                          va_list args) {
+    if (!pass) {
+        printf("  Failed test %d: ", test_num);
+        vprintf(fmt, args);
+        printf("\n");
+    }
+}
+
+static void
+S_format_cfish_test_comment(const char *fmt, ...) {
+    printf("    ");
+
+    va_list args;
+    va_start(args, fmt);
+    vprintf(fmt, args);
+    va_end(args);
+}
+
+static void
+S_format_cfish_batch_comment(const char *fmt, ...) {
+    printf("  ");
+
+    va_list args;
+    va_start(args, fmt);
+    vprintf(fmt, args);
+    va_end(args);
+}
+
+static void
+S_format_cfish_summary(const CFCTest *test) {
+    if (test->num_batches == 0) {
+        printf("No tests planned or run.\n");
+    }
+    else if (test->num_batches_failed == 0) {
+        printf("%d batches passed. %d tests passed.\n",
+               test->num_batches, test->num_tests);
+        printf("Result: PASS\n");
+    }
+    else {
+        printf("%d/%d batches failed. %d/%d tests failed.\n",
+               test->num_batches_failed, test->num_batches,
+               test->num_tests_failed, test->num_tests);
+        printf("Result: FAIL\n");
+    }
+}
+
+static void
+S_format_tap_batch_prologue(const CFCTestBatch *batch) {
+    printf("1..%d\n", batch->num_planned);
+}
+
+static void
+S_format_tap_vtest_result(int pass, int test_num, const char *fmt,
+                         va_list args) {
+    const char *result = pass ? "ok" : "not ok";
+    printf("%s %d - ", result, test_num);
+    vprintf(fmt, args);
+    printf("\n");
+}
+
+static void
+S_format_tap_test_comment(const char *fmt, ...) {
+    printf("#   ");
+
+    va_list args;
+    va_start(args, fmt);
+    vprintf(fmt, args);
+    va_end(args);
+}
+
+static void
+S_format_tap_batch_comment(const char *fmt, ...) {
+    printf("# ");
+
+    va_list args;
+    va_start(args, fmt);
+    vprintf(fmt, args);
+    va_end(args);
+}
+
+static void
+S_format_tap_summary(const CFCTest *test) {
+}
+
+struct CFCParcel*
+CFCTest_parse_parcel(CFCTest *test, CFCParser *parser, const char *src) {
+    CFCBase *result = CFCParser_parse(parser, src);
+    OK(test, result != NULL, "parse '%s'", src);
+    STR_EQ(test, CFCBase_get_cfc_class(result),
+           "Clownfish::CFC::Model::Parcel", "result class of '%s'", src);
+    return (struct CFCParcel*)result;
+}
+
+struct CFCType*
+CFCTest_parse_type(CFCTest *test, CFCParser *parser, const char *src) {
+    CFCBase *result = CFCParser_parse(parser, src);
+    OK(test, result != NULL, "parse '%s'", src);
+    STR_EQ(test, CFCBase_get_cfc_class(result),
+           "Clownfish::CFC::Model::Type", "result class of '%s'", src);
+    return (struct CFCType*)result;
+}
+
+struct CFCVariable*
+CFCTest_parse_variable(CFCTest *test, CFCParser *parser, const char *src) {
+    CFCBase *result = CFCParser_parse(parser, src);
+    OK(test, result != NULL, "parse '%s'", src);
+    STR_EQ(test, CFCBase_get_cfc_class(result),
+           "Clownfish::CFC::Model::Variable", "result class of '%s'", src);
+    return (struct CFCVariable*)result;
+}
+
+struct CFCParamList*
+CFCTest_parse_param_list(CFCTest *test, CFCParser *parser, const char *src) {
+    CFCBase *result = CFCParser_parse(parser, src);
+    OK(test, result != NULL, "parse '%s'", src);
+    STR_EQ(test, CFCBase_get_cfc_class(result),
+           "Clownfish::CFC::Model::ParamList", "result class of '%s'", src);
+    return (struct CFCParamList*)result;
+}
+
+struct CFCFunction*
+CFCTest_parse_function(CFCTest *test, CFCParser *parser, const char *src) {
+    CFCBase *result = CFCParser_parse(parser, src);
+    OK(test, result != NULL, "parse '%s'", src);
+    STR_EQ(test, CFCBase_get_cfc_class(result),
+           "Clownfish::CFC::Model::Function", "result class of '%s'", src);
+    return (struct CFCFunction*)result;
+}
+
+struct CFCMethod*
+CFCTest_parse_method(CFCTest *test, CFCParser *parser, const char *src) {
+    CFCBase *result = CFCParser_parse(parser, src);
+    OK(test, result != NULL, "parse '%s'", src);
+    STR_EQ(test, CFCBase_get_cfc_class(result),
+           "Clownfish::CFC::Model::Method", "result class of '%s'", src);
+    return (struct CFCMethod*)result;
+}
+
+struct CFCClass*
+CFCTest_parse_class(CFCTest *test, CFCParser *parser, const char *src) {
+    CFCBase *result = CFCParser_parse(parser, src);
+    OK(test, result != NULL, "parse class");
+    STR_EQ(test, CFCBase_get_cfc_class(result),
+           "Clownfish::CFC::Model::Class", "result class");
+    return (struct CFCClass*)result;
+}
+
+time_t
+CFCTest_get_file_mtime(const char *path) {
+    struct stat buf;
+    if (stat(path, &buf)) {
+        CFCUtil_die("Can't stat '%s': %s", path, strerror(errno));
+    }
+    return buf.st_mtime;
+}
+
+#if defined(CHY_HAS_UTIME_H)
+
+#include <utime.h>
+
+void
+CFCTest_set_file_times(const char *path, time_t time) {
+    struct utimbuf buf;
+    buf.actime  = time;
+    buf.modtime = time;
+    if (utime(path, &buf)) {
+        CFCUtil_die("Can't set file time of '%s': %s", path, strerror(errno));
+    }
+}
+
+#elif defined(CHY_HAS_WINDOWS_H)
+
+#include <windows.h>
+
+void
+CFCTest_set_file_times(const char *path, time_t time) {
+    HANDLE handle = CreateFile(path, GENERIC_WRITE, FILE_SHARE_READ, NULL,
+                               OPEN_EXISTING, 0, NULL);
+    if (handle == INVALID_HANDLE_VALUE) {
+        CFCUtil_die("Can't open '%s': %u", path, GetLastError());
+    }
+    uint64_t ticks = 10000000 * (UINT64_C(11644473600) + time);
+    FILETIME file_time;
+    file_time.dwLowDateTime  = (DWORD)ticks;
+    file_time.dwHighDateTime = (DWORD)(ticks >> 32);
+    if (!SetFileTime(handle, &file_time, &file_time, &file_time)) {
+        CFCUtil_die("Can't set file time of '%s': %u", path, GetLastError());
+    }
+    CloseHandle(handle);
+}
+
+#else
+
+#error Need either utime.h or windows.h
+
+#endif
+

http://git-wip-us.apache.org/repos/asf/lucy/blob/9898c864/clownfish/compiler/src/CFCTest.h
----------------------------------------------------------------------
diff --git a/clownfish/compiler/src/CFCTest.h b/clownfish/compiler/src/CFCTest.h
new file mode 100644
index 0000000..dbb3a17
--- /dev/null
+++ b/clownfish/compiler/src/CFCTest.h
@@ -0,0 +1,174 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/** Clownfish::CFC::Test - An object to collect results from test runs and
+ * print summaries in multiple formats.
+ */
+
+#ifndef H_CFCTEST
+#define H_CFCTEST
+
+#include <time.h>
+
+#ifdef CFC_USE_TEST_MACROS
+  #define OK      CFCTest_test_true
+  #define STR_EQ  CFCTest_test_string_equals
+  #define INT_EQ  CFCTest_test_int_equals
+#endif
+
+typedef struct CFCTest CFCTest;
+
+typedef struct CFCTestBatch {
+    const char *name;
+    int num_planned;
+    void (*run)(CFCTest *test);
+} CFCTestBatch;
+
+struct CFCClass;
+struct CFCFunction;
+struct CFCMethod;
+struct CFCParamList;
+struct CFCParcel;
+struct CFCParser;
+struct CFCType;
+struct CFCVariable;
+
+/** Create a new test object.
+ *
+ * @param formatter_name Name of the built-in formatter that should be used
+ * to create the output. Supported values are "tap" for TAP output and
+ * "clownfish" for the generic Clownfish format.
+ * @return a new test object.
+ */
+CFCTest*
+CFCTest_new(const char *formatter_name);
+
+CFCTest*
+CFCTest_init(CFCTest *self, const char *formatter_name);
+
+void
+CFCTest_destroy(CFCTest *self);
+
+/** Run all test batches.
+ *
+ * @return true if all tests were successful.
+ */
+int
+CFCTest_run_all(CFCTest *self);
+
+/** Run a test batch by name.
+ *
+ * @param name Name of the test batch.
+ * @return true if all tests in the batch were successful.
+ */
+int
+CFCTest_run_batch(CFCTest *self, const char *name);
+
+/* Collect result of a test.
+ *
+ * @param cond Test condition. True if the test succeeded, false if it failed.
+ * @param fmt printf-like format string describing the test.
+ */
+void
+CFCTest_test_true(CFCTest *self, int cond, const char *fmt, ...);
+
+/* Test strings for equality and collect result.
+ *
+ * @param result Result string to be tested.
+ * @param expected Expected result string.
+ * @param fmt printf-like format string describing the test.
+ */
+void
+CFCTest_test_string_equals(CFCTest *self, const char *result,
+                           const char *expected, const char *fmt, ...);
+
+/* Test integers for equality and collect result.
+ *
+ * @param result Result integer to be tested.
+ * @param expected Expected result integer.
+ * @param fmt printf-like format string describing the test.
+ */
+void
+CFCTest_test_int_equals(CFCTest *self, long result, long expected,
+                        const char *fmt, ...);
+
+/* Skip tests.
+ *
+ * @param num Number of tests to skip.
+ */
+void
+CFCTest_skip(CFCTest *self, int num);
+
+/* Finish testing.
+ *
+ * @return true if tests were run and all tests were successful.
+ */
+int
+CFCTest_finish(CFCTest *self);
+
+/* Utility functions for tests. */
+
+struct CFCParcel*
+CFCTest_parse_parcel(CFCTest *test, struct CFCParser *parser, const char *src);
+
+struct CFCType*
+CFCTest_parse_type(CFCTest *test, struct CFCParser *parser, const char *src);
+
+struct CFCVariable*
+CFCTest_parse_variable(CFCTest *test, struct CFCParser *parser,
+                       const char *src);
+
+struct CFCParamList*
+CFCTest_parse_param_list(CFCTest *test, struct CFCParser *parser,
+                         const char *src);
+
+struct CFCFunction*
+CFCTest_parse_function(CFCTest *test, struct CFCParser *parser,
+                       const char *src);
+
+struct CFCMethod*
+CFCTest_parse_method(CFCTest *test, struct CFCParser *parser, const char *src);
+
+struct CFCClass*
+CFCTest_parse_class(CFCTest *test, struct CFCParser *parser, const char *src);
+
+time_t
+CFCTest_get_file_mtime(const char *path);
+
+void
+CFCTest_set_file_times(const char *path, time_t time);
+
+/* Test batch structs. */
+
+extern const CFCTestBatch CFCTEST_BATCH_CLASS;
+extern const CFCTestBatch CFCTEST_BATCH_C_BLOCK;
+extern const CFCTestBatch CFCTEST_BATCH_DOCU_COMMENT;
+extern const CFCTestBatch CFCTEST_BATCH_FILE;
+extern const CFCTestBatch CFCTEST_BATCH_FILE_SPEC;
+extern const CFCTestBatch CFCTEST_BATCH_FUNCTION;
+extern const CFCTestBatch CFCTEST_BATCH_HIERARCHY;
+extern const CFCTestBatch CFCTEST_BATCH_METHOD;
+extern const CFCTestBatch CFCTEST_BATCH_PARAM_LIST;
+extern const CFCTestBatch CFCTEST_BATCH_PARCEL;
+extern const CFCTestBatch CFCTEST_BATCH_PARSER;
+extern const CFCTestBatch CFCTEST_BATCH_SYMBOL;
+extern const CFCTestBatch CFCTEST_BATCH_TYPE;
+extern const CFCTestBatch CFCTEST_BATCH_UTIL;
+extern const CFCTestBatch CFCTEST_BATCH_VARIABLE;
+extern const CFCTestBatch CFCTEST_BATCH_VERSION;
+
+#endif /* H_CFCTEST */
+

http://git-wip-us.apache.org/repos/asf/lucy/blob/9898c864/clownfish/compiler/src/CFCTestCBlock.c
----------------------------------------------------------------------
diff --git a/clownfish/compiler/src/CFCTestCBlock.c b/clownfish/compiler/src/CFCTestCBlock.c
new file mode 100644
index 0000000..6061d44
--- /dev/null
+++ b/clownfish/compiler/src/CFCTestCBlock.c
@@ -0,0 +1,60 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define CFC_USE_TEST_MACROS
+#include "CFCBase.h"
+#include "CFCCBlock.h"
+#include "CFCParser.h"
+#include "CFCTest.h"
+
+static void
+S_run_tests(CFCTest *test);
+
+const CFCTestBatch CFCTEST_BATCH_C_BLOCK = {
+    "Clownfish::CFC::Model::CBlock",
+    4,
+    S_run_tests
+};
+
+static void
+S_run_tests(CFCTest *test) {
+    CFCParser *parser = CFCParser_new();
+
+    {
+        CFCCBlock *block = CFCCBlock_new("int foo;");
+        STR_EQ(test, CFCCBlock_get_contents(block), "int foo;",
+               "get_contents");
+        CFCBase_decref((CFCBase*)block);
+    }
+
+    {
+        const char *cblock_string =
+            " __C__\n"
+            "#define FOO_BAR 1\n"
+            "__END_C__  ";
+        CFCBase *result = CFCParser_parse(parser, cblock_string);
+        OK(test, result != NULL, "parse cblock");
+        STR_EQ(test, CFCBase_get_cfc_class(result),
+               "Clownfish::CFC::Model::CBlock", "result class of cblock");
+        CFCCBlock *block = (CFCCBlock*)result;
+        STR_EQ(test, CFCCBlock_get_contents(block), "#define FOO_BAR 1\n",
+               "parse embed_c");
+        CFCBase_decref((CFCBase*)block);
+    }
+
+    CFCBase_decref((CFCBase*)parser);
+}
+

http://git-wip-us.apache.org/repos/asf/lucy/blob/9898c864/clownfish/compiler/src/CFCTestClass.c
----------------------------------------------------------------------
diff --git a/clownfish/compiler/src/CFCTestClass.c b/clownfish/compiler/src/CFCTestClass.c
new file mode 100644
index 0000000..80f008a
--- /dev/null
+++ b/clownfish/compiler/src/CFCTestClass.c
@@ -0,0 +1,357 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <string.h>
+
+#define CFC_USE_TEST_MACROS
+#include "CFCBase.h"
+#include "CFCClass.h"
+#include "CFCFileSpec.h"
+#include "CFCFunction.h"
+#include "CFCMethod.h"
+#include "CFCParamList.h"
+#include "CFCParcel.h"
+#include "CFCParser.h"
+#include "CFCSymbol.h"
+#include "CFCTest.h"
+#include "CFCType.h"
+#include "CFCUtil.h"
+#include "CFCVariable.h"
+
+static void
+S_run_tests(CFCTest *test);
+
+static int
+S_has_symbol(CFCSymbol **symbols, const char *micro_sym);
+
+const CFCTestBatch CFCTEST_BATCH_CLASS = {
+    "Clownfish::CFC::Model::Class",
+    83,
+    S_run_tests
+};
+
+static void
+S_run_tests(CFCTest *test) {
+    CFCParser *parser = CFCParser_new();
+
+    CFCParcel *neato = CFCParcel_new("Neato", NULL, NULL);
+    CFCFileSpec *file_spec = CFCFileSpec_new(".", "Foo/FooJr", 0);
+
+    CFCVariable *thing;
+    CFCVariable *widget;
+    CFCFunction *tread_water;
+
+    {
+        CFCType *thing_type = CFCTest_parse_type(test, parser, "Thing*");
+        thing = CFCVariable_new(neato, NULL, "Foo", NULL, "thing",
+                                thing_type, 0);
+
+        CFCType *widget_type = CFCTest_parse_type(test, parser, "Widget*");
+        widget = CFCVariable_new(NULL, NULL, "Widget", NULL, "widget",
+                                 widget_type, 0);
+
+        CFCType *return_type = CFCTest_parse_type(test, parser, "void");
+        CFCParamList *param_list
+            = CFCTest_parse_param_list(test, parser, "()");
+        tread_water
+            = CFCFunction_new(neato, NULL, "Foo", NULL, "tread_water",
+                              return_type, param_list, NULL, 0);
+
+        CFCBase_decref((CFCBase*)thing_type);
+        CFCBase_decref((CFCBase*)widget_type);
+        CFCBase_decref((CFCBase*)return_type);
+        CFCBase_decref((CFCBase*)param_list);
+    }
+
+    CFCClass *foo
+        = CFCClass_create(neato, NULL, "Foo", NULL, NULL, NULL, NULL, NULL,
+                          0, 0);
+    CFCClass_add_function(foo, tread_water);
+    CFCClass_add_member_var(foo, thing);
+    CFCClass_add_inert_var(foo, widget);
+
+    {
+        CFCClass *should_be_foo = CFCClass_fetch_singleton(neato, "Foo");
+        OK(test, should_be_foo == foo, "fetch_singleton");
+    }
+
+    CFCClass *foo_jr
+        = CFCClass_create(neato, NULL, "Foo::FooJr", NULL, NULL, NULL, NULL,
+                          "Foo", 0, 0);
+    CFCClass_add_attribute(foo_jr, "dumpable", "1");
+    OK(test, CFCClass_has_attribute(foo_jr, "dumpable"), "has_attribute");
+    STR_EQ(test, CFCClass_get_struct_sym(foo_jr), "FooJr",
+           "get_struct_sym");
+    STR_EQ(test, CFCClass_full_struct_sym(foo_jr), "neato_FooJr",
+           "full_struct_sym");
+
+    CFCClass *final_foo
+        = CFCClass_create(neato, NULL, "Foo::FooJr::FinalFoo", NULL, NULL, NULL,
+                          file_spec, "Foo::FooJr", 1, 0);
+    CFCClass_add_attribute(final_foo, "dumpable", "1");
+    OK(test, CFCClass_final(final_foo), "final");
+    STR_EQ(test, CFCClass_include_h(final_foo), "Foo/FooJr.h",
+           "include_h uses path_part");
+    STR_EQ(test, CFCClass_get_parent_class_name(final_foo), "Foo::FooJr",
+           "get_parent_class_name");
+
+    {
+        CFCParcel *parsed_neato
+            = CFCTest_parse_parcel(test, parser, "parcel Neato;");
+        CFCBase_decref((CFCBase*)parsed_neato);
+    }
+
+    CFCParser_set_class_name(parser, "Foo");
+    CFCMethod *do_stuff
+        = CFCTest_parse_method(test, parser, "void Do_Stuff(Foo *self);");
+    CFCClass_add_method(foo, do_stuff);
+
+    CFCClass_add_child(foo, foo_jr);
+    CFCClass_add_child(foo_jr, final_foo);
+    CFCClass_grow_tree(foo);
+
+    OK(test, CFCClass_get_parent(foo_jr) == foo, "grow_tree, one level" );
+    OK(test, CFCClass_get_parent(final_foo) == foo_jr,
+       "grow_tree, two levels");
+    OK(test, CFCClass_fresh_method(foo, "Do_Stuff") == do_stuff,
+       "fresh_method");
+    OK(test, CFCClass_method(foo_jr, "Do_Stuff") == do_stuff,
+       "inherited method");
+    OK(test, CFCClass_fresh_method(foo_jr, "Do_Stuff") == NULL,
+       "inherited method not 'fresh'");
+    OK(test, CFCMethod_final(CFCClass_method(final_foo, "Do_Stuff")),
+       "Finalize inherited method");
+    OK(test, !CFCMethod_final(CFCClass_method(foo_jr, "Do_Stuff")),
+       "Don't finalize method in parent");
+
+    {
+        CFCVariable **inert_vars = CFCClass_inert_vars(foo);
+        OK(test, inert_vars[0] == widget, "inert_vars[0]");
+        OK(test, inert_vars[1] == NULL, "inert_vars[1]");
+
+        CFCFunction **functions = CFCClass_functions(foo);
+        OK(test, functions[0] == tread_water, "functions[0]");
+        OK(test, functions[1] == NULL, "functions[1]");
+
+        CFCMethod **methods = CFCClass_methods(foo);
+        OK(test, methods[0] == do_stuff, "methods[0]");
+        OK(test, methods[1] == NULL, "methods[1]");
+
+        CFCMethod **fresh_methods = CFCClass_fresh_methods(foo);
+        OK(test, fresh_methods[0] == do_stuff, "fresh_methods[0]");
+        OK(test, fresh_methods[1] == NULL, "fresh_methods[1]");
+
+        CFCVariable **fresh_member_vars = CFCClass_fresh_member_vars(foo);
+        OK(test, fresh_member_vars[0] == thing, "fresh_member_vars[0]");
+        OK(test, fresh_member_vars[1] == NULL, "fresh_member_vars[1]");
+
+        FREEMEM(fresh_methods);
+        FREEMEM(fresh_member_vars);
+    }
+
+    {
+        CFCVariable **member_vars = CFCClass_member_vars(foo_jr);
+        OK(test, member_vars[0] == thing, "member_vars[0]");
+        OK(test, member_vars[1] == NULL, "member_vars[1]");
+
+        CFCFunction **functions = CFCClass_functions(foo_jr);
+        OK(test, functions[0] == NULL, "functions[0]");
+
+        CFCVariable **fresh_member_vars = CFCClass_fresh_member_vars(foo_jr);
+        OK(test, fresh_member_vars[0] == NULL, "fresh_member_vars[0]");
+
+        CFCVariable **inert_vars = CFCClass_inert_vars(foo_jr);
+        OK(test, inert_vars[0] == NULL, "inert_vars[0]");
+
+        FREEMEM(fresh_member_vars);
+    }
+
+    {
+        CFCMethod **fresh_methods = CFCClass_fresh_methods(final_foo);
+        OK(test, fresh_methods[0] == NULL, "fresh_methods[0]");
+        FREEMEM(fresh_methods);
+    }
+
+    {
+        const char *autocode = CFCClass_get_autocode(foo_jr);
+        OK(test, strstr(autocode, "load") != NULL, "autogenerate Dump/Load");
+    }
+
+    {
+        CFCClass **ladder = CFCClass_tree_to_ladder(foo);
+        OK(test, ladder[0] == foo, "ladder[0]");
+        OK(test, ladder[1] == foo_jr, "ladder[1]");
+        OK(test, ladder[2] == final_foo, "ladder[2]");
+        OK(test, ladder[3] == NULL, "ladder[3]");
+        FREEMEM(ladder);
+    }
+
+    {
+        CFCClass *final_class
+            = CFCTest_parse_class(test, parser, "final class Iamfinal { }");
+        OK(test, CFCClass_final(final_class), "class modifer: final");
+        CFCClass *inert_class
+            = CFCTest_parse_class(test, parser, "inert class Iaminert { }");
+        OK(test, CFCClass_inert(inert_class), "class modifer: inert");
+
+        CFCBase_decref((CFCBase*)final_class);
+        CFCBase_decref((CFCBase*)inert_class);
+    }
+
+    {
+        static const char *names[2] = { "Fooble", "Foo::FooJr::FooIII" };
+        for (int i = 0; i < 2; ++i) {
+            const char *name = names[i];
+            char *class_src
+                = CFCUtil_sprintf("class Fu::%s inherits %s { }", name, name);
+            CFCClass *klass = CFCTest_parse_class(test, parser, class_src);
+            STR_EQ(test, CFCClass_get_parent_class_name(klass), name,
+                   "class_inheritance: %s", name);
+            FREEMEM(class_src);
+            CFCBase_decref((CFCBase*)klass);
+        }
+    }
+
+    {
+        const char *class_src =
+            "public class Foo::Foodie cnick Foodie inherits Foo {\n"
+            "    private int num;\n"
+            "}\n";
+        CFCClass *klass = CFCTest_parse_class(test, parser, class_src);
+        CFCSymbol **member_vars = (CFCSymbol**)CFCClass_member_vars(klass);
+        OK(test, S_has_symbol(member_vars, "num"),
+           "parsed private member var");
+
+        CFCBase_decref((CFCBase*)klass);
+    }
+
+    {
+        const char *class_src =
+            "/**\n"
+            " * Bow wow.\n"
+            " *\n"
+            " * Wow wow wow.\n"
+            " */\n"
+            "public class Animal::Dog inherits Animal : lovable : drooly {\n"
+            "    public inert Dog* init(Dog *self, CharBuf *name,\n"
+            "                           CharBuf *fave_food);\n"
+            "    inert uint32_t count();\n"
+            "    inert uint64_t num_dogs;\n"
+            "\n"
+            "    private CharBuf *name;\n"
+            "    private bool     likes_to_go_fetch;\n"
+            "    private void     Chase_Tail(Dog *self);\n"
+            "\n"
+            "    ChewToy *squishy;\n"
+            "\n"
+            "    void               Destroy(Dog *self);\n"
+            "    public CharBuf*    Bark(Dog *self);\n"
+            "    public void        Eat(Dog *self);\n"
+            "    public void        Bite(Dog *self, Enemy *enemy);\n"
+            "    public Thing      *Fetch(Dog *self, Thing *thing);\n"
+            "    public final void  Bury(Dog *self, Bone *bone);\n"
+            "    public Owner      *mom;\n"
+            "    public abstract incremented nullable Thing*\n"
+            "    Scratch(Dog *self);\n"
+            "\n"
+            "    int32_t[1]  flexible_array_at_end_of_struct;\n"
+            "}\n";
+        CFCClass *klass = CFCTest_parse_class(test, parser, class_src);
+
+        CFCSymbol **inert_vars  = (CFCSymbol**)CFCClass_inert_vars(klass);
+        CFCSymbol **member_vars = (CFCSymbol**)CFCClass_member_vars(klass);
+        CFCSymbol **functions   = (CFCSymbol**)CFCClass_functions(klass);
+        CFCSymbol **methods     = (CFCSymbol**)CFCClass_methods(klass);
+        OK(test, S_has_symbol(inert_vars, "num_dogs"), "parsed inert var");
+        OK(test, S_has_symbol(member_vars, "mom"), "parsed public member var");
+        OK(test, S_has_symbol(member_vars, "squishy"),
+           "parsed parcel member var");
+        OK(test, S_has_symbol(functions, "init"), "parsed function");
+        OK(test, S_has_symbol(methods, "chase_tail"), "parsed private method");
+        OK(test, S_has_symbol(methods, "destroy"), "parsed parcel method");
+        OK(test, S_has_symbol(methods, "bury"), "parsed public method");
+        OK(test, S_has_symbol(methods, "scratch"),
+           "parsed public abstract nullable method");
+
+        CFCMethod *scratch = CFCClass_method(klass, "scratch");
+        OK(test, scratch != NULL, "find method 'scratch'");
+        OK(test, CFCType_nullable(CFCMethod_get_return_type(scratch)),
+           "public abstract incremented nullable flagged as nullable");
+
+        int num_public_methods = 0;
+        for (int i = 0; methods[i]; ++i) {
+            if (CFCSymbol_public(methods[i])) { ++num_public_methods; }
+        }
+        INT_EQ(test, num_public_methods, 6, "pass acl to Method constructor");
+
+        OK(test, CFCClass_has_attribute(klass, "lovable"),
+           "parsed class attribute");
+        OK(test, CFCClass_has_attribute(klass, "drooly"),
+           "parsed second class attribute");
+
+        CFCBase_decref((CFCBase*)klass);
+    }
+
+    {
+        const char *class_src =
+            "parcel inert class Rigor::Mortis cnick Mort {\n"
+            "    parcel inert void lie_still();\n"
+            "}\n";
+        CFCClass *klass = CFCTest_parse_class(test, parser, class_src);
+        OK(test, CFCClass_inert(klass),
+           "inert modifier parsed and passed to constructor");
+
+        CFCBase_decref((CFCBase*)klass);
+    }
+
+    {
+        const char *class_src =
+            "final class Ultimo {\n"
+            "    /** Throws an error.\n"
+            "     */\n"
+            "    void Say_Never(Ultimo *self);\n"
+            "}\n";
+        CFCClass *klass = CFCTest_parse_class(test, parser, class_src);
+        OK(test, CFCClass_final(klass), "final class_declaration");
+        CFCBase_decref((CFCBase*)klass);
+    }
+
+    CFCBase_decref((CFCBase*)parser);
+    CFCBase_decref((CFCBase*)neato);
+    CFCBase_decref((CFCBase*)file_spec);
+    CFCBase_decref((CFCBase*)thing);
+    CFCBase_decref((CFCBase*)widget);
+    CFCBase_decref((CFCBase*)tread_water);
+    CFCBase_decref((CFCBase*)foo);
+    CFCBase_decref((CFCBase*)foo_jr);
+    CFCBase_decref((CFCBase*)final_foo);
+    CFCBase_decref((CFCBase*)do_stuff);
+
+    CFCClass_clear_registry();
+    CFCParcel_reap_singletons();
+}
+
+static int
+S_has_symbol(CFCSymbol **symbols, const char *micro_sym) {
+    for (int i = 0; symbols[i]; ++i) {
+        if (strcmp(CFCSymbol_micro_sym(symbols[i]), micro_sym) == 0) {
+            return 1;
+        }
+    }
+
+    return 0;
+}
+

http://git-wip-us.apache.org/repos/asf/lucy/blob/9898c864/clownfish/compiler/src/CFCTestDocuComment.c
----------------------------------------------------------------------
diff --git a/clownfish/compiler/src/CFCTestDocuComment.c b/clownfish/compiler/src/CFCTestDocuComment.c
new file mode 100644
index 0000000..dba5f3c
--- /dev/null
+++ b/clownfish/compiler/src/CFCTestDocuComment.c
@@ -0,0 +1,107 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define CFC_USE_TEST_MACROS
+#include "CFCBase.h"
+#include "CFCDocuComment.h"
+#include "CFCParser.h"
+#include "CFCTest.h"
+#include "CFCUtil.h"
+
+static void
+S_run_tests(CFCTest *test);
+
+const CFCTestBatch CFCTEST_BATCH_DOCU_COMMENT = {
+    "Clownfish::CFC::Model::DocuComment",
+    15,
+    S_run_tests
+};
+
+static void
+S_run_tests(CFCTest *test) {
+    CFCDocuComment *docucomment;
+
+    docucomment = CFCDocuComment_parse("/** foo. */");
+    OK(test, docucomment != NULL, "parse");
+    CFCBase_decref((CFCBase*)docucomment);
+
+    CFCParser *parser = CFCParser_new();
+    const char *text =
+        "/**\n"
+        " * Brief description.  Long description.\n"
+        " *\n"
+        " * More long description.\n"
+        " *\n"
+        " * @param foo A foo.\n"
+        " * @param bar A bar.\n"
+        " *\n"
+        " * @param baz A baz.\n"
+        " * @return a return value.\n"
+        " */\n";
+    CFCBase *result = CFCParser_parse(parser, text);
+    OK(test, result != NULL, "parse with CFCParser");
+    const char *klass = CFCBase_get_cfc_class(result);
+    STR_EQ(test, klass, "Clownfish::CFC::Model::DocuComment", "result class");
+    docucomment = (CFCDocuComment*)result;
+
+    const char *brief_desc = CFCDocuComment_get_brief(docucomment);
+    const char *brief_expect = "Brief description.";
+    STR_EQ(test, brief_desc, brief_expect, "brief description");
+
+    const char *long_desc = CFCDocuComment_get_long(docucomment);
+    const char *long_expect =
+        "Long description.\n"
+        "\n"
+        "More long description.";
+    STR_EQ(test, long_desc, long_expect, "long description");
+
+    const char *description = CFCDocuComment_get_description(docucomment);
+    char *desc_expect = CFCUtil_sprintf("%s  %s", brief_expect, long_expect);
+    STR_EQ(test, description, desc_expect, "description");
+    FREEMEM(desc_expect);
+
+    const char **param_names = CFCDocuComment_get_param_names(docucomment);
+    int num_param_names = 0;
+    for (const char **p = param_names; *p; ++p) { ++num_param_names; }
+    INT_EQ(test, num_param_names, 3, "number of param names");
+    const char *param_names_expect[3] = { "foo", "bar", "baz" };
+    for (int i = 0; i < 3; ++i) {
+        STR_EQ(test, param_names[i], param_names_expect[i],
+               "param name %d", i);
+    }
+
+    const char **param_docs = CFCDocuComment_get_param_docs(docucomment);
+    int num_param_docs = 0;
+    for (const char **p = param_docs; *p; ++p) { ++num_param_docs; }
+    INT_EQ(test, num_param_docs, 3, "number of param docs");
+    const char *param_docs_expect[3] = { "A foo.", "A bar.", "A baz." };
+    const char *param_docs_test[3] = {
+        "@param terminated by @",
+        "@param terminated by empty line",
+        "@param terminated next element, @return"
+    };
+    for (int i = 0; i < 3; ++i) {
+        STR_EQ(test, param_docs[i], param_docs_expect[i], param_docs_test[i]);
+    }
+
+    const char *retval = CFCDocuComment_get_retval(docucomment);
+    const char *retval_expect = "a return value.";
+    STR_EQ(test, retval, retval_expect, "retval");
+
+    CFCBase_decref((CFCBase*)docucomment);
+    CFCBase_decref((CFCBase*)parser);
+}
+

http://git-wip-us.apache.org/repos/asf/lucy/blob/9898c864/clownfish/compiler/src/CFCTestFile.c
----------------------------------------------------------------------
diff --git a/clownfish/compiler/src/CFCTestFile.c b/clownfish/compiler/src/CFCTestFile.c
new file mode 100644
index 0000000..2e1edc5
--- /dev/null
+++ b/clownfish/compiler/src/CFCTestFile.c
@@ -0,0 +1,132 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "charmony.h"
+
+#define CFC_USE_TEST_MACROS
+#include "CFCBase.h"
+#include "CFCClass.h"
+#include "CFCFile.h"
+#include "CFCFileSpec.h"
+#include "CFCParser.h"
+#include "CFCTest.h"
+#include "CFCType.h"
+#include "CFCUtil.h"
+#include "CFCVariable.h"
+
+static void
+S_run_tests(CFCTest *test);
+
+const CFCTestBatch CFCTEST_BATCH_FILE = {
+    "Clownfish::CFC::Model::File",
+    19,
+    S_run_tests
+};
+
+static void
+S_run_tests(CFCTest *test) {
+#define STUFF_THING "Stuff" CHY_DIR_SEP "Thing"
+
+    CFCParser *parser = CFCParser_new();
+    CFCFileSpec *file_spec = CFCFileSpec_new(".", STUFF_THING, 0);
+
+    {
+        const char *string =
+            "parcel Stuff;\n"
+            "class Stuff::Thing {\n"
+            "    Foo *foo;\n"
+            "    Bar *bar;\n"
+            "}\n"
+            "__C__\n"
+            "int foo;\n"
+            "__END_C__\n";
+        CFCFile *file = CFCParser_parse_file(parser, string, file_spec);
+
+        STR_EQ(test, CFCFile_get_source_dir(file), ".", "get_source_dir");
+        STR_EQ(test, CFCFile_get_path_part(file), STUFF_THING,
+               "get_path_part");
+        OK(test, !CFCFile_included(file), "included");
+
+        STR_EQ(test, CFCFile_guard_name(file), "H_STUFF_THING", "guard_name");
+        STR_EQ(test, CFCFile_guard_start(file),
+               "#ifndef H_STUFF_THING\n#define H_STUFF_THING 1\n",
+               "guard_start");
+        STR_EQ(test, CFCFile_guard_close(file), "#endif /* H_STUFF_THING */\n",
+               "guard_close");
+
+        OK(test, !CFCFile_get_modified(file), "modified false at start");
+        CFCFile_set_modified(file, 1);
+        OK(test, CFCFile_get_modified(file), "set_modified, get_modified");
+
+#define PATH_TO_STUFF_THING \
+    "path" CHY_DIR_SEP \
+    "to" CHY_DIR_SEP \
+    "Stuff" CHY_DIR_SEP \
+    "Thing"
+
+        char *cfh_path = CFCFile_cfh_path(file, "path/to");
+        STR_EQ(test, cfh_path, PATH_TO_STUFF_THING ".cfh", "cfh_path");
+        FREEMEM(cfh_path);
+        char *c_path = CFCFile_c_path(file, "path/to");
+        STR_EQ(test, c_path, PATH_TO_STUFF_THING ".c", "c_path");
+        FREEMEM(c_path);
+        char *h_path = CFCFile_h_path(file, "path/to");
+        STR_EQ(test, h_path, PATH_TO_STUFF_THING ".h", "h_path");
+        FREEMEM(h_path);
+
+        CFCClass **classes = CFCFile_classes(file);
+        OK(test, classes[0] != NULL && classes[1] == NULL,
+           "classes() filters blocks");
+        CFCVariable **member_vars = CFCClass_member_vars(classes[0]);
+        CFCType *foo_type = CFCVariable_get_type(member_vars[0]);
+        STR_EQ(test, CFCType_get_specifier(foo_type), "stuff_Foo",
+               "file production picked up parcel def");
+        CFCType *bar_type = CFCVariable_get_type(member_vars[1]);
+        STR_EQ(test, CFCType_get_specifier(bar_type), "stuff_Bar",
+               "parcel def is sticky");
+
+        CFCBase **blocks = CFCFile_blocks(file);
+        STR_EQ(test, CFCBase_get_cfc_class(blocks[0]),
+               "Clownfish::CFC::Model::Parcel", "blocks[0]");
+        STR_EQ(test, CFCBase_get_cfc_class(blocks[1]),
+               "Clownfish::CFC::Model::Class", "blocks[1]");
+        STR_EQ(test, CFCBase_get_cfc_class(blocks[2]),
+               "Clownfish::CFC::Model::CBlock", "blocks[2]");
+        OK(test, blocks[3] == NULL, "blocks[3]");
+
+        CFCBase_decref((CFCBase*)file);
+    }
+
+    {
+        const char *string =
+            "class Stuff::Thing {\n"
+            "    Foo *foo;\n"
+            "    Bar *bar;\n"
+            "}\n";
+        CFCFile *file = CFCParser_parse_file(parser, string, file_spec);
+        CFCClass **classes = CFCFile_classes(file);
+        CFCVariable **member_vars = CFCClass_member_vars(classes[0]);
+        CFCType *foo_type = CFCVariable_get_type(member_vars[0]);
+        STR_EQ(test, CFCType_get_specifier(foo_type), "Foo",
+               "file production resets parcel");
+
+        CFCBase_decref((CFCBase*)file);
+    }
+
+    CFCBase_decref((CFCBase*)file_spec);
+    CFCBase_decref((CFCBase*)parser);
+}
+

http://git-wip-us.apache.org/repos/asf/lucy/blob/9898c864/clownfish/compiler/src/CFCTestFileSpec.c
----------------------------------------------------------------------
diff --git a/clownfish/compiler/src/CFCTestFileSpec.c b/clownfish/compiler/src/CFCTestFileSpec.c
new file mode 100644
index 0000000..7fdb2d8
--- /dev/null
+++ b/clownfish/compiler/src/CFCTestFileSpec.c
@@ -0,0 +1,53 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define CFC_USE_TEST_MACROS
+#include "CFCBase.h"
+#include "CFCFileSpec.h"
+#include "CFCTest.h"
+
+static void
+S_run_tests(CFCTest *test);
+
+const CFCTestBatch CFCTEST_BATCH_FILE_SPEC = {
+    "Clownfish::CFC::Model::FileSpec",
+    4,
+    S_run_tests
+};
+
+static void
+S_run_tests(CFCTest *test) {
+    {
+        CFCFileSpec *file_spec
+            = CFCFileSpec_new("Clownfish/_include", "Stuff/Thing", 0);
+        STR_EQ(test, CFCFileSpec_get_source_dir(file_spec),
+               "Clownfish/_include", "get_source_dir");
+        STR_EQ(test, CFCFileSpec_get_path_part(file_spec),
+               "Stuff/Thing", "get_path_part");
+        OK(test, !CFCFileSpec_included(file_spec), "not included");
+
+        CFCBase_decref((CFCBase*)file_spec);
+    }
+
+    {
+        CFCFileSpec *file_spec
+            = CFCFileSpec_new("Clownfish/_include", "Stuff/Thing", 1);
+        OK(test, CFCFileSpec_included(file_spec), "included");
+
+        CFCBase_decref((CFCBase*)file_spec);
+    }
+}
+

http://git-wip-us.apache.org/repos/asf/lucy/blob/9898c864/clownfish/compiler/src/CFCTestFunction.c
----------------------------------------------------------------------
diff --git a/clownfish/compiler/src/CFCTestFunction.c b/clownfish/compiler/src/CFCTestFunction.c
new file mode 100644
index 0000000..4cd6967
--- /dev/null
+++ b/clownfish/compiler/src/CFCTestFunction.c
@@ -0,0 +1,76 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define CFC_USE_TEST_MACROS
+#include "CFCBase.h"
+#include "CFCFunction.h"
+#include "CFCParamList.h"
+#include "CFCParcel.h"
+#include "CFCParser.h"
+#include "CFCTest.h"
+#include "CFCType.h"
+
+static void
+S_run_tests(CFCTest *test);
+
+const CFCTestBatch CFCTEST_BATCH_FUNCTION = {
+    "Clownfish::CFC::Model::Function",
+    11,
+    S_run_tests
+};
+
+static void
+S_run_tests(CFCTest *test) {
+    CFCParser *parser = CFCParser_new();
+    CFCParcel *neato_parcel
+        = CFCTest_parse_parcel(test, parser, "parcel Neato;");
+
+    {
+        CFCType *return_type = CFCTest_parse_type(test, parser, "Obj*");
+        CFCParamList *param_list
+            = CFCTest_parse_param_list(test, parser, "(int32_t some_num)");
+        CFCFunction *func
+            = CFCFunction_new(neato_parcel, NULL, "Neato::Foo", "Foo",
+                              "return_an_obj", return_type, param_list,
+                              NULL, 0);
+        OK(test, func != NULL, "new");
+
+        CFCBase_decref((CFCBase*)return_type);
+        CFCBase_decref((CFCBase*)param_list);
+        CFCBase_decref((CFCBase*)func);
+    }
+
+    {
+        CFCParser_set_class_name(parser, "Neato::Obj");
+        CFCParser_set_class_cnick(parser, "Obj");
+        static const char *func_strings[2] = {
+            "inert int running_count(int biscuit);",
+            "public inert Hash* init_fave_hash(int32_t num_buckets,"
+            " bool o_rly);"
+        };
+        for (int i = 0; i < 2; ++i) {
+            CFCFunction *func
+                = CFCTest_parse_function(test, parser, func_strings[i]);
+            CFCBase_decref((CFCBase*)func);
+        }
+    }
+
+    CFCBase_decref((CFCBase*)neato_parcel);
+    CFCBase_decref((CFCBase*)parser);
+
+    CFCParcel_reap_singletons();
+}
+