Skip to content

Commit 7a96efe

Browse files
jonathanpeppersjonpryor
authored andcommitted
[generator] initial unit test support and refactoring (#282)
Context: https://github.com/grendello/JavaBindingGenerator @grendello has an initial prototype of a "re-imagined" `generator`. Our current goal is to refactor the current `generator` and slowly migrate the existing code to look something like the new one. It obviously is going to take a while, so we need to do this in a fashion where we can keep things working while making changes over time. Eventually we may get to a place where we can import @grendello's existing project to be used within `generator`. The high-level changes we should first adopt from the new `generator`: - An object model that uses POCOs, such as the `Java.Interop.Bindings.Syntax` hierarchy @grendello came up with - Logical separation between the `api.xml` parsing and the code generation - The possibility of "unit" tests, instead of the slow/bulky tests we have currently, which are in fact "integration" tests - The possibility for new backends that will have breaking changes, but allow us to move `generator` forward into the future This PR is the first step in achieving this, here is a breakdown of the changes: - Moved existing tests to an `Integration-Tests` folder - New tests will go in the `Unit-Tests` folder if they are indeed unit tests - `[assembly: InternalsVisibleTo("generator-Tests")]` will need to be added to `generator` so that we can write unit tests against `internal` classes and methods - Instances of `StreamWriter sw` are changed to `TextWriter writer` as needed. This allows us to use a `StringWriter` in unit tests - A first set of unit tests are validating the simplest cases such as class handles, class invoker handles, and fields - A set of "support types" are added to the `generator-Tests` project. Once the existing code is reasonably under unit test, these types can completely go away as we switch the object model over to POCOs. - Any generation methods of `Field` have been moved to the `CodeGenerator` base class. This allows us to get `Field` closer to a POCO. - The constructor of `Parameter` is now `internal` so it can be instantiated from unit tests - Two sets of new tests are added: `JavaInteropCodeGeneratorTests` and `XamarinAndroidCodeGeneratorTests`, unit tests within take around 1ms, so we can add as many of these as we like - Usage of `CodeGenerator.ContextType` are being refactored during the migration, as it seems to be a code smell. While writing each unit test, I could easily discover the `ContextTypes` stack usage because exceptions are thrown. Hopefully, we can remove `ContextType` completely as refactoring continues, and just pass the required value through an additional parameter. NOTE: in this PR, I've (hopefully) not altered the output of `generator`. I have noticed a few bugs/oddities in the output, but plan to fix these in future PRs after we have these tests. Miscellaneous changes: - VS for Windows unit test runner wants to add a `<Service />` MSBuild item, I'm not sure what this is for, but it is a hassle to keep discarding it - Updated `.gitignore` for VS for Windows
1 parent 9c6a550 commit 7a96efe

38 files changed

+960
-295
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@ xa-gendarme.html
99
packages
1010
.vs/
1111
*.userprefs
12+
*.user
1213
Resource.designer.cs

tools/generator/CodeGenerator.cs

Lines changed: 46 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -695,22 +695,57 @@ protected CodeGenerator ()
695695
{
696696
}
697697

698-
internal abstract void WriteClassHandle (ClassGen type, StreamWriter sw, string indent, CodeGenerationOptions opt, bool requireNew);
698+
internal abstract void WriteClassHandle (ClassGen type, TextWriter writer, string indent, CodeGenerationOptions opt, bool requireNew);
699699

700-
internal abstract void WriteClassHandle (InterfaceGen type, StreamWriter sw, string indent, CodeGenerationOptions opt, string declaringType);
700+
internal abstract void WriteClassHandle (InterfaceGen type, TextWriter writer, string indent, CodeGenerationOptions opt, string declaringType);
701701

702-
internal abstract void WriteClassInvokerHandle (ClassGen type, StreamWriter sw, string indent, CodeGenerationOptions opt, string declaringType);
703-
internal abstract void WriteInterfaceInvokerHandle (InterfaceGen type, StreamWriter sw, string indent, CodeGenerationOptions opt, string declaringType);
702+
internal abstract void WriteClassInvokerHandle (ClassGen type, TextWriter writer, string indent, CodeGenerationOptions opt, string declaringType);
703+
internal abstract void WriteInterfaceInvokerHandle (InterfaceGen type, TextWriter writer, string indent, CodeGenerationOptions opt, string declaringType);
704704

705-
internal abstract void WriteConstructorIdField (Ctor ctor, StreamWriter sw, string indent, CodeGenerationOptions opt);
706-
internal abstract void WriteConstructorBody (Ctor ctor, StreamWriter sw, string indent, CodeGenerationOptions opt, StringCollection call_cleanup);
705+
internal abstract void WriteConstructorIdField (Ctor ctor, TextWriter writer, string indent, CodeGenerationOptions opt);
706+
internal abstract void WriteConstructorBody (Ctor ctor, TextWriter writer, string indent, CodeGenerationOptions opt, StringCollection call_cleanup);
707707

708-
internal abstract void WriteMethodIdField (Method method, StreamWriter sw, string indent, CodeGenerationOptions opt);
709-
internal abstract void WriteMethodBody (Method method, StreamWriter sw, string indent, CodeGenerationOptions opt);
708+
internal abstract void WriteMethodIdField (Method method, TextWriter writer, string indent, CodeGenerationOptions opt);
709+
internal abstract void WriteMethodBody (Method method, TextWriter writer, string indent, CodeGenerationOptions opt);
710710

711-
internal abstract void WriteFieldIdField (Field field, StreamWriter sw, string indent, CodeGenerationOptions opt);
712-
internal abstract void WriteFieldGetBody (Field field, StreamWriter sw, string indent, CodeGenerationOptions opt);
713-
internal abstract void WriteFieldSetBody (Field field, StreamWriter sw, string indent, CodeGenerationOptions opt);
711+
internal abstract void WriteFieldIdField (Field field, TextWriter writer, string indent, CodeGenerationOptions opt);
712+
internal abstract void WriteFieldGetBody (Field field, TextWriter writer, string indent, CodeGenerationOptions opt, GenBase type);
713+
internal abstract void WriteFieldSetBody (Field field, TextWriter writer, string indent, CodeGenerationOptions opt, GenBase type);
714+
715+
internal virtual void WriteField (Field field, TextWriter writer, string indent, CodeGenerationOptions opt, GenBase type)
716+
{
717+
if (field.IsEnumified)
718+
writer.WriteLine ("[global::Android.Runtime.GeneratedEnum]");
719+
if (field.NeedsProperty) {
720+
string fieldType = field.Symbol.IsArray ? "IList<" + field.Symbol.ElementType + ">" : opt.GetOutputName (field.Symbol.FullName);
721+
WriteFieldIdField (field, writer, indent, opt);
722+
writer.WriteLine ();
723+
writer.WriteLine ("{0}// Metadata.xml XPath field reference: path=\"{1}/field[@name='{2}']\"", indent, type.MetadataXPathReference, field.JavaName);
724+
writer.WriteLine ("{0}[Register (\"{1}\"{2})]", indent, field.JavaName, field.AdditionalAttributeString ());
725+
writer.WriteLine ("{0}{1} {2}{3} {4} {{", indent, field.Visibility, field.IsStatic ? "static " : String.Empty, fieldType, field.Name);
726+
writer.WriteLine ("{0}\tget {{", indent);
727+
WriteFieldGetBody (field, writer, indent + "\t\t", opt, type);
728+
writer.WriteLine ("{0}\t}}", indent);
729+
730+
if (!field.IsConst) {
731+
writer.WriteLine ("{0}\tset {{", indent);
732+
WriteFieldSetBody (field, writer, indent + "\t\t", opt, type);
733+
writer.WriteLine ("{0}\t}}", indent);
734+
}
735+
writer.WriteLine ("{0}}}", indent);
736+
}
737+
else {
738+
writer.WriteLine ("{0}// Metadata.xml XPath field reference: path=\"{1}/field[@name='{2}']\"", indent, type.MetadataXPathReference, field.JavaName);
739+
writer.WriteLine ("{0}[Register (\"{1}\"{2})]", indent, field.JavaName, field.AdditionalAttributeString ());
740+
if (field.IsDeprecated)
741+
writer.WriteLine ("{0}[Obsolete (\"{1}\")]", indent, field.DeprecatedComment);
742+
if (field.Annotation != null)
743+
writer.WriteLine ("{0}{1}", indent, field.Annotation);
744+
745+
// the Value complication is due to constant enum from negative integer value (C# compiler requires explicit parenthesis).
746+
writer.WriteLine ("{0}{1} const {2} {3} = ({2}) {4};", indent, field.Visibility, opt.GetOutputName (field.Symbol.FullName), field.Name, field.Value.Contains ('-') && field.Symbol.FullName.Contains ('.') ? '(' + field.Value + ')' : field.Value);
747+
}
748+
}
714749
}
715750
}
716751

tools/generator/Field.cs

Lines changed: 0 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -234,45 +234,6 @@ internal ParameterList SetParameters {
234234

235235
public string Annotation { get; internal set; }
236236

237-
void GenerateProperty (StreamWriter sw, string indent, CodeGenerationOptions opt, GenBase gen)
238-
{
239-
string type = Symbol.IsArray ? "IList<" + Symbol.ElementType + ">" : opt.GetOutputName (Symbol.FullName);
240-
opt.CodeGenerator.WriteFieldIdField (this, sw, indent, opt);
241-
sw.WriteLine ();
242-
sw.WriteLine ("{0}// Metadata.xml XPath field reference: path=\"{1}/field[@name='{2}']\"", indent, gen.MetadataXPathReference, JavaName);
243-
sw.WriteLine ("{0}[Register (\"{1}\"{2})]", indent, JavaName, this.AdditionalAttributeString ());
244-
sw.WriteLine ("{0}{1} {2}{3} {4} {{", indent, Visibility, IsStatic ? "static " : String.Empty, type, Name);
245-
sw.WriteLine ("{0}\tget {{", indent);
246-
opt.CodeGenerator.WriteFieldGetBody (this, sw, indent + "\t\t", opt);
247-
sw.WriteLine ("{0}\t}}", indent);
248-
249-
if (!IsConst) {
250-
sw.WriteLine ("{0}\tset {{", indent);
251-
opt.CodeGenerator.WriteFieldSetBody (this, sw, indent + "\t\t", opt);
252-
sw.WriteLine ("{0}\t}}", indent);
253-
}
254-
sw.WriteLine ("{0}}}", indent);
255-
}
256-
257-
public void Generate (StreamWriter sw, string indent, CodeGenerationOptions opt, GenBase type)
258-
{
259-
if (IsEnumified)
260-
sw.WriteLine ("[global::Android.Runtime.GeneratedEnum]");
261-
if (NeedsProperty)
262-
GenerateProperty (sw, indent, opt, type);
263-
else {
264-
sw.WriteLine ("{0}// Metadata.xml XPath field reference: path=\"{1}/field[@name='{2}']\"", indent, type.MetadataXPathReference, JavaName);
265-
sw.WriteLine ("{0}[Register (\"{1}\"{2})]", indent, JavaName, this.AdditionalAttributeString ());
266-
if (IsDeprecated)
267-
sw.WriteLine ("{0}[Obsolete (\"{1}\")]", indent, DeprecatedComment);
268-
if (Annotation != null)
269-
sw.WriteLine ("{0}{1}", indent, Annotation);
270-
271-
// the Value complication is due to constant enum from negative integer value (C# compiler requires explicit parenthesis).
272-
sw.WriteLine ("{0}{1} const {2} {3} = ({2}) {4};", indent, Visibility, opt.GetOutputName (Symbol.FullName), Name, Value.Contains ('-') && Symbol.FullName.Contains ('.') ? '(' + Value + ')' : Value);
273-
}
274-
}
275-
276237
public bool Validate (CodeGenerationOptions opt, GenericParameterDefinitionList type_params)
277238
{
278239
symbol = SymbolTable.Lookup (TypeName, type_params);

tools/generator/GenBase.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -577,7 +577,7 @@ public bool GenFields (StreamWriter sw, string indent, CodeGenerationOptions opt
577577
seen.Add (f.Name);
578578
needsProperty = needsProperty || f.NeedsProperty;
579579
sw.WriteLine ();
580-
f.Generate (sw, indent, opt, this);
580+
opt.CodeGenerator.WriteField (f, sw, indent, opt, this);
581581
}
582582
}
583583
return needsProperty;

0 commit comments

Comments
 (0)