QA Plan

From $1

Purpose

This document serves as a guide to testing IronRuby interoperation with .NET classes. It will cover interacting with the CLR, interacting with .NET Base Class Libraries, interacting with custom CLS compliant and non-CLS compliant libraries, and interacting with unsafe CLR. It will not cover COM interoperation, DLR specific interaction, inter-language interaction via the DLR.

Intro

.NET interop is one of our key features. It is also one of our more complex features. We will look at .NET interop in the following areas:

  • Loading .NET Interop
  • Getting .NET Types
  • Using .NET Types
  • Deriving from .NET Types
  • Special .NET Concepts
  • Special Ruby Concepts

CLR to Ruby mapping

For this table, the only mapping rule is that a Namespace.[Namespaces].Class construct in .NET becomes Namespace::[Namespaces]::Class in IronRuby. For .NET enums, the enumerations become IronRuby constants of the type of the enum, so an enum Foo, with enumerations Bar and Baz, the IronRuby structure becomes class Foo, with Foo.Bar = Foo.new and Foo.Baz = Foo.new.

         

CLR

Ruby

Example

Class

Class

System.String => System:: String

Interface

Module

System.Idisposable => System::IDisposable

Namespace

Module

System => System

Enum

Class

System.StringComparison => System::StringComparison

Struct

Class

System.Int32 => System::Int32

Object

Object

         


Delegate

Implicit conversions from Block or proc? Explicit delegate instantiation (Func.new {})

         


Generic

System::Scripting::Actions::TypeGroup

System.Nullable => System::Nullable

Exceptions Mostly mapped to Ruby Exceptions. Should list in the right hierarchy. Message property should be hidden (message on base class needs to be called)

Loading .NET Interop

.Net interop is initiated by calling require or load with any .NET assembly, or using load_assembly with an assembly’s Strong Name. These test cases will be stored in interop/load. Arbitrary .NET assembly should enable cls_visibility.

Scenarios

  • mscorlib
  • .NET BCL assembly
  • .NET custom assembly
  • signed .NET custom assembly (After discussing with Dave Fugate, from IronPython, this is a low pri, as there is not much to be gained)
  • signed assembly from the GAC (Not needed, a .NET BCL assembly is a signed assembly from the GAC)
  • each of the above with a module constructor (low pri) 

Negative Scenarios

  • assembly outside of the load path (probe path) 

Cases

Load requires file name extension. Require can use partial name, but will try to load the .rb file first if it finds one. Dependency assemblies need to be loaded as well. Full and partial paths.

Load Cases

  • single
    • require
    • load
    • load_assembly
  • multiple
    • require, require
    • require, load
    • require, load_assembly
    • load, require
    • load, load
    • load, load_assembly
    • load_assembly, require
    • load_assembly, load
    • load_assembly, load_assembly
  • Aliased assemblies (Ruby/libs)
  • p3 - FQ Assembly name vs file name

Simply Load

  • Attempt to load via each of the single cases
  • Attempt to load multiple assemblies via each of the multiple cases using different assemblies for each load.  Use PIC to identify proper permutations.

Loading Multiple Times

  • Load the same assembly using each of the multiple methods. Use load_assembly for Strong Named assemblies.
  • p2 - Using each of the multiple cases, load the assembly, modify it, then reload it. Discuss this behavior in specific test cases. 
    • Modify top-level type
    • Modify nested type
    • Modify namespaced type
    • Modify namespace
    • Modify one member

Name and Namespace Clashes

Given a loaded type of:

  • NS.C and NS.C<T>
    • Try a type NS
    • Try a type NS<T>
    • Try a type DiffNS.D
    • Try a type DiffNS.D<T>
    • Try a type DiffNS.C
    • Try a type DiffNS.C<T>
    • Try a type NS.C
    • Try a type NS.C<T>
    • Try a type NS.D
    • Try a type NS.D<T>
  • C and C<T>
    • Try a namespace C

Try these with the loaded type coming from .NET, and from Ruby. Clashes with different types (constant C and module C). Merging namespaces should work (Like how .NET and Ruby behave). Lowercase classes and namespaces are not supported (reconsider if user request).

Getting .NET Types

These test cases will be stored in interop/mapping.

Scenarios

  • p3 - Deeply nested types
  • Arrays
    • One dimensional
      • TODO: check on location
      • Array.of(Int).new(10)
      • [].to_clr_array
    • Multi-dimensional
      • Array.of(Int,String).new
    • p2 - Jagged
    • p2 - Non-zero lower bound (Use .NET API)
  • p3 - Type which causes TypeLoad Exception
    • p3 - Assembly which has multiple types, one of which causes the exception
  • p2 - Forwarded type 
  • p3 - Type from Reflection.Emit
  • Classes
    • Abstract class
      • Empty
      • Not Empty
      • Base
      • Derived abstract class
      • Abstract class derived from non-abstract class
      • p2 - Abstract class implementing an interface
    • Sealed class
    • Static class
    • Partial Class (Not a valid test case)
      • All in CS isn’t interesting, gets compiled to one assembly.
      • Partially implemented in IronRuby
    • Generic Class
      • From 0 up to 1 template parameters
      • From 0 up to 2 template parameters
      • investigate what cases will hit new code
    • Class implementing an interface
    • Ruby class derived from each of these
  • Interface
    • Partially implemented interfaces
    • Generic interface
      • I<T> and I<V> where:
        • T and V are the same
        • T and V are different
        • Reference C# spec to see what is and isn't allowed
    • Interface declaring property, indexer, event, method
    • p3 - RubyClass including an interface that derives from an interface
    • Explicit Interface members
    • Overlapping members with a parent class (IFoo declares foo, and A declares foo. Also test with the case of abstract class and abstract method)
    • Ruby class mixing in a CLR interface
  • Enum
    • p3 - Empty
    • p2 - 1 member
    • Multiple members
    Struct
    • Empty
    • Implemented
  • Namespace
    • Unique namespace
    • Namespace collision
      • With other namespace
      • With a class
  • TypeGroup
    • From 0 up to 1 template parameter
    • From 0 up to 2 template parameters
    • From 1 up to 3 template parameters
    • Interface with each of these
    • investigate which of these hit code paths
  • Concrete instance of Typegroup (e.g. Nullable<int>)
    • Using [] notation
    • Using .of() notation
  • Simple types
    • Value Types
    • Reference Types
  • Delegates
  • Instances of
    • CLR simple types
    • CLR classes listed above
    • TypeGroups

Negative Scenarios

Cases

  • Get each of the scenario types. Ensure that mapping works correctly
  • Attempt to get basic types with name conflicts
  • p3 - Attempt to get basic types with non-CLS names
  • p2 - Attempt to get the type of the Template parameter for Generics
  • p2 - Attempt to get the type of a Template parameter that hasn’t been provided
  • Ensure name mapping and name mangling work
    • What cases should this not work for?

Using .NET Types

These test cases will be stored in interop/using.

Scenarios

  • Types in “Getting .NET Types”
  • Concrete instance of TypeGroup with [] notation and an array type
  • A class with multiple constructor overrides

Negative Scenarios

  • Instantiating Abstract class
  • Instantiating static class

Cases

Member Modification

  • Add a method
    • Public
    • Private
    • Protected
    • Overloads
    • Various numbers of args
  • Add a method which conflicts with Ruby keywords
  • Add an overload to an existing method
  • Modify a field
    • Static field
    • Instance field
    • Readonly field
    • Volatile field
    • Const field
  • Remove a property
    • Different casing (should error out)
    • Created in CLR
    • Created in Ruby
  • Remove a property multiple times
  • Remove a method
  • Remove an existing method
  • Remove a method overload (?)
  • Change the visibility of an existing method
  • Instantiate an object and use the above methods.
  • Remove an interface method and test for behavior in regard to the broken interface
  • Call Type.GetMembers() on a modified class to verify added/deleted members.
  • Modify a property defined in an interface
  • Use a property defined in an interface
  • Modify a virtual method
  • Attempt to use a virtual method

Special Parameter types

  • Use methods with different parameter lists. In C#:
    • public void M1(int x, int y) { }
    • public void M2(int x, params int[] y)
    • public void M3(int x, [DefaultParameterValue(5)] int y)
    • public void M6([Optional] int x, [Optional] object y)
    • public void M7([Out] int x)
    • public void M7([ParamsDictionary] IattributesCollection dict)
    • one special parameter
    • two special parameters
    • one normal, one special in both orders
    • multiple parameters of mixed kind
  • Use DefaultParameterValue/Optional attributes
    • Can optional parameters be omitted?
    • Do DefaultParameterValue’s get used?
  • p2 - Parameter names that collide with keywords or methods
  • Use a method with ref and out parameters in all these scenarios

Overloaded Methods

  • Override interesting methods like ToString() and ensure Ruby equivalent #to_str still works.
  • Add/remove an override for a base-class virtual method. Also, hide the base-class method with a new one
  • Add/remove new operator overloads and verify that the operators work in ruby
  • Add/remove operators for unsupported operators in Ruby
  • Add/remove implicitly and explicitly implemented interfaces
  • Pick an override from Ruby
  • Non-generic / generic methods have the same name
    • Activator.CreateInstance scenario
    • G<T>.M(int), G<T>.M(T)
    • G.M(int), G.M<T>(T)
  • Static method and instance method have the same name
    • instance M(C), static M(thisType, C)
  • Same name as explicit interface method

Ruby Considerations

  • Call existing Ruby methods from Object and Kernel on .NET objects
  • Use Ruby’s mixin functionality. For example, include Enumerable and define .each, and test for the existence of other Enumerable methods
  • Attempt to add a Ruby object to a typed .NET array
  • Attempt to assign an object of the wrong type to a field
  • Get an unbound .NET method
  • Direct call of method vs. send :method vs eval method
  • Members that collide with Ruby keywords

Indexers

  • Use a default indexer from within Ruby
  • Read only indexers :[]
  • Write only indexers :[]=
  • Indexer defined in interface
  • Overloaded interface
  • p2 - Indexer defined in VB style
  • Parameter list on indexer

Other

  • Implicit and explicit conversions in Ruby
  • Return a private type
  • p3 - Check for presence of private members with –X:PrivateBinding
  • p2 - Operator overloading and usage

Events/Delegates

  • Define a delegate from Ruby
  • Use a delegate from Ruby
  • Generic delegate types
  • With overloaded method
  • Add a method to a delegate
  • Add a method multiple times
  • Remove a method from a delegate
  • Remove a repeated method from a delegate (one time should remove one occurrence)
  • Remove all methods from the delegate (should be benign)
  • Remove a non-existent method from a delegate with other methods (should be benign)
  • Empty delegate should have an empty invocation list
  • Define an event in an interface
  • Define an event in a class
  • Static events
  • Static delegates
  • Add a delegate as an event
  • Remove a delegate as an event
  • Remove a method as an event
  • Event with Add only
  • Event with Remove only
  • Add a method as an event
  • Add an incompatible delegate and method to an event
  • Call a method with multiple delegates

Special Cases

IronRuby has the ability to modify modules dynamically. This should work for CLR types too. If you mix in a module to a class, create an instance of the modified class, add another method to the module, and use that method in the already existing instance. In pure Ruby, as an example:

    
      module M
        def foo
          &apos;foo&apos;
        end
      end

      class C
        include M
      end

      c = C.new

      c.foo #=> &apos;foo&apos;

      c.bar #=> NoMethodError

      module M
        def bar
          &apos;bar&apos;
        end
      end

      c.bar #=> &apos;bar&apos;
    
  

This should work for all of the above scenarios. You should also be able to add methods to any object that represents itself as a module in Ruby, including interfaces and namespaces. It should also work to reopen a class to add interfaces, or do something to change the typeof. This must be done before .new is called.

Deriving from .NET Types

These test cases will be stored in interop/derivation.

Scenarios

  • Ruby Type deriving from each of the Types in “Getting .NET Types”
  • .NET class that has been reopened in Ruby
  • System.Delegate
  • System.Enum
  • System.ValueType
  • System.Array
  • System.MarshalByRefObject
  • Literal
  • Extensible<T>
  • Nullable<T>
  • Reopen .NET instance
  • RubyClass < DotNetB, RubyClassB < RubyClass

Negative Scenarios

  • Deriving from sealed class

Cases

  • All cases from “Using .NET Types”

Special .NET Concepts

These test cases will live in interop/special.

  • Array Types
    • Creation
    • Operations
      • Indexing
        • Ensure that A[1,2] does a multi-variable lookup
        • Indexing redefinition
    • Pass the array as an argument
  • Operations on an enum type and its members
  • Proper mapping for exception types(See table below)
  • Mapping in both directions between special .NET concepts and Ruby concepts
    • IList, IList<T>, List, ArrayList
    • Hashtable, Dictionary`2, IDictionary
    • IEnumerable, IEnumerable<T>, IEnumerator <-> Enumerable
    • IComparable, IComparable<T> <-> Comparable
    • ISerializable and SerializableAttribute <-> Marshal
    • System.Object.ToString <-> to_s
  • Extensible<T>
  • Operations on namespaces
    • Top level
    • Nested
    • Bottom level
  • Operations on nested classes
  • Operations on Assembly and AssemblyBuilder
  • CLRString vs Ruby String
  • Delegates vs Ruby Proc’s
  • Getting at the underlying .NET type
  • Implicit and explicit conversions
  • p2 - DataBinding to Ruby objects
  • Exceptions
    • Thread.Abort
    • StackOverflow
    • OutOfMemory
    • SystemExit
      • Needs to be handled coming from .NET and Ruby
    • Cross Runtime exceptions
    • Thread.Kill on a thread called from hosted code (we translate Thread.Abort to the User intended exception sometimes. (Currently in SourceUnitTree))
      • Thread.Kill is implemented via Thread.abort. Need to ensure there are no type leaks

   Equality and hashing

Object equality and hashing are fundamental properties of objects. The Ruby API for comparing and hashing objects is Object#== (or Object#eql?) and Object#hash respectively. The CLR APIs are System.Object.Equals and System.Object.GetHashCode respectively. IronRuby does an automatic mapping between the two concepts so that Ruby objects can be compared and hashsed from static MSIL code and vice versa.

When Ruby code calls #== and #hash
  • If the object is a Ruby object, the default implementations of #== and #hash get called. The default implementations call System.Object.ReferenceEquals and System.Runtime.CompileServices.RuntimeHelpers.GetHashCode respectively.
  • If the object is a CLR object, System.Object.Equals and System.Object.GetHashCode respectively get called on the CLR object.
  • If the object is a Ruby subclass object inheriting from a CLR class, the CLR's class's implementation of System.Object.Equals and System.Object.GetHashCode will get called if the Ruby subclass does not define #== and #hash. If the Ruby subclass defines #== and #hash, those will be called instead.
When static MSIL code calls System.Object.Equals and System.Object.GetHashCode
  • If the object is a Ruby objects, the Ruby object will direct the call to #== and #hash. If the Ruby object has implementations for these methods, they will be called. Otherwise, the default implementation mentioned above gets called.
  • If the object is a Ruby subclass object inheriting from a CLR class,  the CLR's class's implementation of System.Object.Equals and System.Object.GetHashCode will get called if the Ruby subclass does not define #== and #hash. If the Ruby subclass defines #== and #hash, those will be called instead.
Hashing of mutable objects

The CLR expects that System.Object.GetHashCode always returns the same value for a given object. If this invariant is not maintained, using the object as a key in a System.Collections.Generic.Dictionary<K,V> will misbehave. Ruby allows #hash to return different results, and relies on the user to deal with the scenario of using the object as a key in a Hash. The mapping above between the Ruby and CLR concepts of equality and hashing means that CLR code that deals with Ruby objects has to be aware of the issue. If static MSIL code uses a Ruby object as a the key in a Dictionary<K,V>, unexpected behavior might happen.

To reduce the chances of this happenning when using common Ruby types, IronRuby does not map #hash to GetHashCode for Array and Hash. For other Ruby classes, the user can provide separate implementations for #== and Equals, and #hash and GetHashCode if the Ruby class is mutable but also needs to be usable as a key in a Dictionary<K,V>.

  • Ruby exception
 .NET exception  Comment
Exception              
  NoMemoryError              
  ScriptError              
    LoadError              
    NotImplementedError   System.NotImplementedException       
    SyntaxError              
  SignalException              
    Interrupt              
  StandardError              
    ArgumentError   System.ArgumentException       
      IOError   System.IO.IOException       
    EOFError              
    IndexError   System.IndexOutOfRangeException       
    LocalJumpError   IronRuby.Builtins.LocalJumpError       
    NameError   System.MemberAccessException       
      NoMethodError              
    RangeError   System.ArgumentOutOfRangeException       
      FloatDomainError              
    RegexpError              
    RuntimeError              
    SecurityError  System.Security.SecurityException        
    SystemCallError   System.Runtime.InteropServices.ExternalException       
    SystemStackError              
    ThreadError              
    TypeError   System.InvalidOperationException   Why not InvalidCastException?
    ZeroDivisionError              
  SystemExit              
  fatal              
                    
                    
                    

Special Ruby Concepts

  • Aliasing .NET methods 
  • module_function on .NET classes, should not work as an instance method not defined on System::Object
  • Method resolution on Ruby methods that should be added as ancestors of .NET types (Kernel, Module, Object)
Tags:
 
Images (1)
Viewing 1 - 1 of 1 images | View All
Mapping between Ruby exceptions and CLR exceptions
Mapping between Ruby exceptions and CLR exceptions
Exception...  Actions
Comments (0)
You must login to post a comment.

 
SourceForge.net