gir_ffi 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. data/DESIGN.rdoc +54 -0
  2. data/History.txt +3 -0
  3. data/README.rdoc +59 -0
  4. data/Rakefile +21 -0
  5. data/TODO.rdoc +40 -0
  6. data/examples/01_empty_window.rb +15 -0
  7. data/examples/02_hello_world.rb +30 -0
  8. data/examples/03_upgraded_hello_world.rb +45 -0
  9. data/examples/demo_ffi_inherited_layout.rb +21 -0
  10. data/examples/demo_ffi_nested_struct.rb +17 -0
  11. data/examples/demo_ffi_safe_inherited_layout.rb +43 -0
  12. data/examples/hard_coded.rb +144 -0
  13. data/lib/gir_ffi.rb +47 -0
  14. data/lib/gir_ffi/allocation_helper.rb +12 -0
  15. data/lib/gir_ffi/arg_helper.rb +77 -0
  16. data/lib/gir_ffi/base.rb +23 -0
  17. data/lib/gir_ffi/builder.rb +159 -0
  18. data/lib/gir_ffi/builder_helper.rb +32 -0
  19. data/lib/gir_ffi/class_base.rb +11 -0
  20. data/lib/gir_ffi/class_builder.rb +116 -0
  21. data/lib/gir_ffi/constructor_definition_builder.rb +20 -0
  22. data/lib/gir_ffi/function_definition_builder.rb +148 -0
  23. data/lib/gir_ffi/g_error.rb +8 -0
  24. data/lib/gir_ffi/g_type.rb +14 -0
  25. data/lib/gir_ffi/i_arg_info.rb +16 -0
  26. data/lib/gir_ffi/i_base_info.rb +45 -0
  27. data/lib/gir_ffi/i_callable_info.rb +18 -0
  28. data/lib/gir_ffi/i_callback_info.rb +7 -0
  29. data/lib/gir_ffi/i_constant_info.rb +6 -0
  30. data/lib/gir_ffi/i_enum_info.rb +13 -0
  31. data/lib/gir_ffi/i_field_info.rb +10 -0
  32. data/lib/gir_ffi/i_flags_info.rb +5 -0
  33. data/lib/gir_ffi/i_function_info.rb +16 -0
  34. data/lib/gir_ffi/i_interface_info.rb +7 -0
  35. data/lib/gir_ffi/i_object_info.rb +50 -0
  36. data/lib/gir_ffi/i_property_info.rb +7 -0
  37. data/lib/gir_ffi/i_registered_type_info.rb +8 -0
  38. data/lib/gir_ffi/i_repository.rb +108 -0
  39. data/lib/gir_ffi/i_signal_info.rb +7 -0
  40. data/lib/gir_ffi/i_struct_info.rb +22 -0
  41. data/lib/gir_ffi/i_type_info.rb +25 -0
  42. data/lib/gir_ffi/i_union_info.rb +7 -0
  43. data/lib/gir_ffi/i_value_info.rb +8 -0
  44. data/lib/gir_ffi/i_vfunc_info.rb +7 -0
  45. data/lib/gir_ffi/lib.rb +174 -0
  46. data/lib/gir_ffi/lib_c.rb +11 -0
  47. data/lib/gir_ffi/method_missing_definition_builder.rb +62 -0
  48. data/lib/gir_ffi/module_builder.rb +66 -0
  49. data/lib/gir_ffi/overrides/gtk.rb +12 -0
  50. data/lib/gir_ffi/version.rb +4 -0
  51. data/tasks/bones.rake +87 -0
  52. data/tasks/notes.rake +134 -0
  53. data/tasks/post_load.rake +25 -0
  54. data/tasks/setup.rb +138 -0
  55. data/tasks/test.rake +22 -0
  56. data/test/arg_helper_test.rb +112 -0
  57. data/test/builder_test.rb +328 -0
  58. data/test/constructor_definition_builder_test.rb +19 -0
  59. data/test/function_definition_builder_test.rb +60 -0
  60. data/test/g_type_test.rb +22 -0
  61. data/test/girffi_test.rb +11 -0
  62. data/test/gtk_overrides_test.rb +22 -0
  63. data/test/i_repository_test.rb +54 -0
  64. data/test/test_helper.rb +39 -0
  65. metadata +174 -0
@@ -0,0 +1,134 @@
1
+ # The following code is copied straight from Bones 2.5.1
2
+ #
3
+
4
+ begin
5
+ require 'facets/ansicode'
6
+ HAVE_COLOR = true
7
+ rescue LoadError
8
+ HAVE_COLOR = false
9
+ end
10
+
11
+ module Bones
12
+
13
+ # A helper class used to find and display any annotations in a collection of
14
+ # project files.
15
+ #
16
+ class AnnotationExtractor
17
+
18
+ class Annotation < Struct.new(:line, :tag, :text)
19
+ # Returns a string representation of the annotation. If the
20
+ # <tt>:tag</tt> parameter is given as +true+, then the annotation tag
21
+ # will be included in the string.
22
+ #
23
+ def to_s( opts = {} )
24
+ s = "[%3d] " % line
25
+ s << "[#{tag}] " if opts[:tag]
26
+ s << text
27
+ end
28
+ end
29
+
30
+ # Enumerate all the annoations for the given _project_ and _tag_. This
31
+ # will search for all athe annotations and display them on standard
32
+ # output.
33
+ #
34
+ def self.enumerate( project, tag, id = nil, opts = {} )
35
+ extractor = new(project, tag, id)
36
+ extractor.display(extractor.find, opts)
37
+ end
38
+
39
+ attr_reader :tag, :project, :id
40
+
41
+ # Creates a new annotation extractor configured to use the _project_ open
42
+ # strcut and to search for the given _tag_ (which can be more than one tag
43
+ # via a regular expression 'or' operation -- i.e. THIS|THAT|OTHER)
44
+ #
45
+ def initialize( project, tag, id)
46
+ @project = project
47
+ @tag = tag
48
+ @id = @id_rgxp = nil
49
+
50
+ unless id.nil? or id.empty?
51
+ @id = id
52
+ @id_rgxp = Regexp.new(Regexp.escape(id), Regexp::IGNORECASE)
53
+ end
54
+ end
55
+
56
+ # Iterate over all the files in the project and extract annotations from
57
+ # the those files. Returns the results as a hash for display.
58
+ #
59
+ def find
60
+ results = {}
61
+ rgxp = %r/(#{tag}):?\s*(.*?)(?:\s*(?:-?%>|\*+\/))?$/o
62
+
63
+ extensions = project.notes.extensions.dup
64
+ exclude = if project.notes.exclude.empty? then nil
65
+ else Regexp.new(project.notes.exclude.join('|')) end
66
+
67
+ manifest.each do |fn|
68
+ next if exclude && exclude =~ fn
69
+ next unless extensions.include? File.extname(fn)
70
+ results.update(extract_annotations_from(fn, rgxp))
71
+ end
72
+
73
+ results
74
+ end
75
+
76
+ # Extract any annotations from the given _file_ using the regular
77
+ # expression _pattern_ provided.
78
+ #
79
+ def extract_annotations_from( file, pattern )
80
+ lineno = 0
81
+ result = File.readlines(file).inject([]) do |list, line|
82
+ lineno += 1
83
+ next list unless m = pattern.match(line)
84
+ next list << Annotation.new(lineno, m[1], m[2]) unless id
85
+
86
+ text = m[2]
87
+ if text =~ @id_rgxp
88
+ text.gsub!(@id_rgxp) {|str| ANSICode.green(str)} if HAVE_COLOR
89
+ list << Annotation.new(lineno, m[1], text)
90
+ end
91
+ list
92
+ end
93
+ result.empty? ? {} : { file => result }
94
+ end
95
+
96
+ # Print the results of the annotation extraction to the screen. If the
97
+ # <tt>:tags</tt> option is set to +true+, then the annotation tag will be
98
+ # displayed.
99
+ #
100
+ def display( results, opts = {} )
101
+ results.keys.sort.each do |file|
102
+ puts "#{file}:"
103
+ results[file].each do |note|
104
+ puts " * #{note.to_s(opts)}"
105
+ end
106
+ puts
107
+ end
108
+ end
109
+
110
+ end # class AnnotationExtractor
111
+ end # module Bones
112
+
113
+ desc "Enumerate all annotations"
114
+ task :notes do |t|
115
+ id = if t.application.top_level_tasks.length > 1
116
+ t.application.top_level_tasks.slice!(1..-1).join(' ')
117
+ end
118
+ Bones::AnnotationExtractor.enumerate(
119
+ PROJ, PROJ.notes.tags.join('|'), id, :tag => true)
120
+ end
121
+
122
+ namespace :notes do
123
+ PROJ.notes.tags.each do |tag|
124
+ desc "Enumerate all #{tag} annotations"
125
+ task tag.downcase.to_sym do |t|
126
+ id = if t.application.top_level_tasks.length > 1
127
+ t.application.top_level_tasks.slice!(1..-1).join(' ')
128
+ end
129
+ Bones::AnnotationExtractor.enumerate(PROJ, tag, id)
130
+ end
131
+ end
132
+ end
133
+
134
+ # EOF
@@ -0,0 +1,25 @@
1
+
2
+ # This file does not define any rake tasks. It is used to load some project
3
+ # settings if they are not defined by the user.
4
+
5
+ PROJ.exclude << ["^#{Regexp.escape(PROJ.ignore_file)}$"]
6
+
7
+ flatten_arrays = lambda do |this,os|
8
+ os.instance_variable_get(:@table).each do |key,val|
9
+ next if key == :dependencies \
10
+ or key == :development_dependencies
11
+ case val
12
+ when Array; val.flatten!
13
+ when OpenStruct; this.call(this,val)
14
+ end
15
+ end
16
+ end
17
+ flatten_arrays.call(flatten_arrays,PROJ)
18
+
19
+ PROJ.changes ||= paragraphs_of(PROJ.history_file, 0..1).join("\n\n")
20
+
21
+ PROJ.description ||= paragraphs_of(PROJ.readme_file, 'description').join("\n\n")
22
+
23
+ PROJ.summary ||= PROJ.description.split('.').first
24
+
25
+ # EOF
@@ -0,0 +1,138 @@
1
+
2
+ require 'rake'
3
+ require 'rake/clean'
4
+ require 'fileutils'
5
+ require 'ostruct'
6
+ require 'find'
7
+
8
+ # TODO: Clean up bones' task set to remove unwanted parts.
9
+
10
+ PROJ = OpenStruct.new(
11
+ # Project Defaults
12
+ :name => nil,
13
+ :summary => nil,
14
+ :description => nil,
15
+ :changes => nil,
16
+ :authors => nil,
17
+ :email => nil,
18
+ :url => "\000",
19
+ :version => ENV['VERSION'] || '0.0.0',
20
+ :exclude => %w(tmp$ bak$ ~$ CVS \.svn/ \.git/ ^pkg/),
21
+ :release_name => ENV['RELEASE'],
22
+
23
+ # System Defaults
24
+ :ruby_opts => %w(-w),
25
+ :libs => [],
26
+ :history_file => 'History.txt',
27
+ :readme_file => 'README.txt',
28
+ :ignore_file => '.bnsignore',
29
+
30
+ # File Annotations
31
+ :notes => OpenStruct.new(
32
+ :exclude => %w(^tasks/setup\.rb$),
33
+ :extensions => %w(.txt .rb .erb .rdoc) << '',
34
+ :tags => %w(FIXME OPTIMIZE TODO)
35
+ ),
36
+
37
+ # Test::Unit
38
+ :test => OpenStruct.new(
39
+ :files => FileList['test/**/*_test.rb'],
40
+ :file => 'test/all.rb',
41
+ :opts => []
42
+ )
43
+ )
44
+
45
+ # Load the other rake files in the tasks folder
46
+ tasks_dir = File.expand_path(File.dirname(__FILE__))
47
+ post_load_fn = File.join(tasks_dir, 'post_load.rake')
48
+ rakefiles = Dir.glob(File.join(tasks_dir, '*.rake')).sort
49
+ rakefiles.unshift(rakefiles.delete(post_load_fn)).compact!
50
+ import(*rakefiles)
51
+
52
+ # Setup the project libraries
53
+ %w(lib ext).each {|dir| PROJ.libs << dir if test ?d, dir}
54
+
55
+ %w(facets/ansicode).each do |lib|
56
+ begin
57
+ require lib
58
+ Object.instance_eval {const_set "HAVE_#{lib.tr('/','_').upcase}", true}
59
+ rescue LoadError
60
+ Object.instance_eval {const_set "HAVE_#{lib.tr('/','_').upcase}", false}
61
+ end
62
+ end
63
+
64
+ # Reads a file at +path+ and spits out an array of the +paragraphs+
65
+ # specified.
66
+ #
67
+ # changes = paragraphs_of('History.txt', 0..1).join("\n\n")
68
+ # summary, *description = paragraphs_of('README.txt', 3, 3..8)
69
+ #
70
+ def paragraphs_of( path, *paragraphs )
71
+ title = String === paragraphs.first ? paragraphs.shift : nil
72
+ ary = File.read(path).delete("\r").split(/\n\n+/)
73
+
74
+ result = if title
75
+ tmp, matching = [], false
76
+ rgxp = %r/^=+\s*#{Regexp.escape(title)}/i
77
+ paragraphs << (0..-1) if paragraphs.empty?
78
+
79
+ ary.each do |val|
80
+ if val =~ rgxp
81
+ break if matching
82
+ matching = true
83
+ rgxp = %r/^=+/i
84
+ elsif matching
85
+ tmp << val
86
+ end
87
+ end
88
+ tmp
89
+ else ary end
90
+
91
+ result.values_at(*paragraphs)
92
+ end
93
+
94
+ # Adds the given arguments to the include path if they are not already there
95
+ #
96
+ def ensure_in_path( *args )
97
+ args.each do |path|
98
+ path = File.expand_path(path)
99
+ $:.unshift(path) if test(?d, path) and not $:.include?(path)
100
+ end
101
+ end
102
+
103
+ # Scans the current working directory and creates a list of files that are
104
+ # candidates to be in the manifest.
105
+ #
106
+ def manifest
107
+ files = []
108
+ exclude = PROJ.exclude.dup
109
+ comment = %r/^\s*#/
110
+
111
+ # process the ignore file and add the items there to the exclude list
112
+ if test(?f, PROJ.ignore_file)
113
+ ary = []
114
+ File.readlines(PROJ.ignore_file).each do |line|
115
+ next if line =~ comment
116
+ line.chomp!
117
+ line.strip!
118
+ next if line.nil? or line.empty?
119
+
120
+ glob = line =~ %r/\*\./ ? File.join('**', line) : line
121
+ Dir.glob(glob).each {|fn| ary << "^#{Regexp.escape(fn)}"}
122
+ end
123
+ exclude.concat ary
124
+ end
125
+
126
+ # generate a regular expression from the exclude list
127
+ exclude = Regexp.new(exclude.join('|'))
128
+
129
+ Find.find '.' do |path|
130
+ path.sub! %r/^(\.\/|\/)/o, ''
131
+ next unless test ?f, path
132
+ next if path =~ exclude
133
+ files << path
134
+ end
135
+ files.sort!
136
+ end
137
+
138
+ # EOF
@@ -0,0 +1,22 @@
1
+
2
+ if test(?e, PROJ.test.file) or not PROJ.test.files.to_a.empty?
3
+ require 'rake/testtask'
4
+
5
+ namespace :test do
6
+
7
+ Rake::TestTask.new(:run) do |t|
8
+ t.libs = PROJ.libs
9
+ t.test_files = if test(?f, PROJ.test.file) then [PROJ.test.file]
10
+ else PROJ.test.files end
11
+ t.ruby_opts += PROJ.ruby_opts
12
+ t.ruby_opts += PROJ.test.opts
13
+ end
14
+
15
+ end # namespace :test
16
+
17
+ desc 'Alias to test:run'
18
+ task :test => 'test:run'
19
+
20
+ end
21
+
22
+ # EOF
@@ -0,0 +1,112 @@
1
+ require File.expand_path('test_helper.rb', File.dirname(__FILE__))
2
+ require 'gir_ffi/arg_helper'
3
+
4
+ class ArgHelperTest < Test::Unit::TestCase
5
+ context "The int_to_inoutptr method's return value" do
6
+ setup do
7
+ @result = GirFFI::ArgHelper.int_to_inoutptr 24
8
+ end
9
+
10
+ should "be a FFI::Pointer" do
11
+ assert_equal "FFI::Pointer", @result.class.to_s
12
+ end
13
+
14
+ should "hold a pointer to the correct input value" do
15
+ assert_equal 24, @result.read_int
16
+ end
17
+ end
18
+
19
+ context "The string_array_to_inoutptr method" do
20
+ context "when called with an array of strings" do
21
+ setup do
22
+ @result = GirFFI::ArgHelper.string_array_to_inoutptr ["foo", "bar", "baz"]
23
+ end
24
+
25
+ should "return a FFI::Pointer" do
26
+ assert_equal "FFI::Pointer", @result.class.to_s
27
+ end
28
+
29
+ should "return a pointer to an array of pointers to strings" do
30
+ ptr = @result.read_pointer
31
+ ary = ptr.read_array_of_pointer(3)
32
+ assert_equal ["foo", "bar", "baz"], ary.map {|p| p.read_string}
33
+ end
34
+ end
35
+ context "when called with nil" do
36
+ should "return nil" do
37
+ assert_nil GirFFI::ArgHelper.string_array_to_inoutptr nil
38
+ end
39
+ end
40
+ end
41
+
42
+ context "The outptr_to_int method" do
43
+ setup do
44
+ @ptr = GirFFI::AllocationHelper.safe_malloc FFI.type_size(:int)
45
+ @ptr.write_int 342
46
+ end
47
+
48
+ should "retrieve the correct integer value" do
49
+ assert_equal 342, GirFFI::ArgHelper.outptr_to_int(@ptr)
50
+ end
51
+ end
52
+
53
+ context "The outptr_to_string_array method" do
54
+ context "when called with a valid pointer to a string array" do
55
+ setup do
56
+ p = GirFFI::AllocationHelper.safe_malloc FFI.type_size(:pointer) * 2
57
+ p.write_array_of_pointer ["one", "two"].map {|str|
58
+ len = str.bytesize
59
+ GirFFI::AllocationHelper.safe_malloc(len + 1).write_string(str).put_char(len, 0)
60
+ }
61
+ @ptr = GirFFI::AllocationHelper.safe_malloc FFI.type_size(:pointer)
62
+ @ptr.write_pointer p
63
+ end
64
+
65
+ should "return the string array" do
66
+ assert_equal ["one", "two"],
67
+ GirFFI::ArgHelper.outptr_to_string_array(@ptr, 2)
68
+ end
69
+ end
70
+
71
+ context "when called with a pointer to a string array containing a null pointer" do
72
+ setup do
73
+ ptrs = ["one", "two"].map {|str|
74
+ len = str.bytesize
75
+ GirFFI::AllocationHelper.safe_malloc(len + 1).write_string(str).put_char(len, 0)
76
+ }
77
+ ptrs << nil
78
+ p = GirFFI::AllocationHelper.safe_malloc FFI.type_size(:pointer) * 3
79
+ p.write_array_of_pointer ptrs
80
+ @ptr = GirFFI::AllocationHelper.safe_malloc FFI.type_size(:pointer)
81
+ @ptr.write_pointer p
82
+ end
83
+
84
+ should "return render the null pointer as nil" do
85
+ assert_equal ["one", "two", nil],
86
+ GirFFI::ArgHelper.outptr_to_string_array(@ptr, 3)
87
+ end
88
+ end
89
+
90
+ context "when called with nil" do
91
+ should "return nil" do
92
+ assert_nil GirFFI::ArgHelper.outptr_to_string_array(nil, 0)
93
+ end
94
+ end
95
+ end
96
+
97
+ context "The object_to_inptr method" do
98
+ context "when called with an object implementing to_ptr" do
99
+ should "return the result of to_ptr" do
100
+ obj = Object.new
101
+ def obj.to_ptr; :test_value; end
102
+ assert_equal :test_value, GirFFI::ArgHelper.object_to_inptr(obj)
103
+ end
104
+ end
105
+
106
+ context "when called with nil" do
107
+ should "return nil" do
108
+ assert_equal nil, GirFFI::ArgHelper.object_to_inptr(nil)
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,328 @@
1
+ require File.expand_path('test_helper.rb', File.dirname(__FILE__))
2
+ require 'gir_ffi/builder'
3
+
4
+ class BuilderTest < Test::Unit::TestCase
5
+ context "The GirFFI::Builder module" do
6
+ # TODO: Use gir's sample Everything library for testing instead.
7
+ context "building GObject::Object" do
8
+ setup do
9
+ GirFFI::Builder.build_class 'GObject', 'Object', 'NS1'
10
+ end
11
+
12
+ should "create a method_missing method for the class" do
13
+ ms = NS1::GObject::Object.instance_methods(false).map(&:to_sym)
14
+ assert_contains ms, :method_missing
15
+ end
16
+
17
+ should "create a Lib module in the parent namespace ready to attach functions from gobject-2.0" do
18
+ gir = GirFFI::IRepository.default
19
+ expected = gir.shared_library 'GObject'
20
+ assert_same_elements [*expected], NS1::GObject::Lib.ffi_libraries.map(&:name)
21
+ end
22
+
23
+ should "create an array CALLBACKS inside the GObject::Lib module" do
24
+ assert_equal [], NS1::GObject::Lib::CALLBACKS
25
+ end
26
+
27
+ should "not replace existing classes" do
28
+ oldclass = NS1::GObject::Object
29
+ GirFFI::Builder.build_class 'GObject', 'Object', 'NS1'
30
+ assert_equal oldclass, NS1::GObject::Object
31
+ end
32
+ end
33
+
34
+ context "building Gtk::Window" do
35
+ setup do
36
+ GirFFI::Builder.build_class 'Gtk', 'Window', 'NS3'
37
+ end
38
+
39
+ should "build Gtk namespace" do
40
+ assert NS3::Gtk.const_defined? :Lib
41
+ assert NS3::Gtk.respond_to? :method_missing
42
+ end
43
+
44
+ should "build parent classes also" do
45
+ assert NS3::Gtk.const_defined? :Widget
46
+ assert NS3::Gtk.const_defined? :Object
47
+ assert NS3.const_defined? :GObject
48
+ assert NS3::GObject.const_defined? :InitiallyUnowned
49
+ assert NS3::GObject.const_defined? :Object
50
+ end
51
+
52
+ should "set up inheritence chain" do
53
+ assert_equal [
54
+ NS3::Gtk::Window,
55
+ NS3::Gtk::Bin,
56
+ NS3::Gtk::Container,
57
+ NS3::Gtk::Widget,
58
+ NS3::Gtk::Object,
59
+ NS3::GObject::InitiallyUnowned,
60
+ NS3::GObject::Object
61
+ ], NS3::Gtk::Window.ancestors[0..6]
62
+ end
63
+
64
+ should "create a Gtk::Window#to_ptr method" do
65
+ assert_contains NS3::Gtk::Window.instance_methods.map(&:to_sym), :to_ptr
66
+ end
67
+
68
+ should "attach gtk_window_new to Gtk::Lib" do
69
+ assert NS3::Gtk::Lib.respond_to? :gtk_window_new
70
+ end
71
+
72
+ should "result in Gtk::Window.new to succeed" do
73
+ assert_nothing_raised {NS3::Gtk::Window.new(:toplevel)}
74
+ end
75
+ end
76
+
77
+ context "building Gtk" do
78
+ setup do
79
+ GirFFI::Builder.build_module 'Gtk', 'NS2'
80
+ end
81
+
82
+ should "create a Lib module ready to attach functions from gtk-x11-2.0" do
83
+ # The Gtk module has more than one library on my current machine.
84
+ gir = GirFFI::IRepository.default
85
+ expected = (gir.shared_library 'Gtk').split(',')
86
+ assert_same_elements expected, NS2::Gtk::Lib.ffi_libraries.map(&:name)
87
+ end
88
+
89
+ should "create an array CALLBACKS inside the Gtk::Lib module" do
90
+ assert_equal [], NS2::Gtk::Lib::CALLBACKS
91
+ end
92
+
93
+ should "not replace existing module" do
94
+ oldmodule = NS2::Gtk
95
+ GirFFI::Builder.build_module 'Gtk', 'NS2'
96
+ assert_equal oldmodule, NS2::Gtk
97
+ end
98
+
99
+ should "not replace existing Lib module" do
100
+ oldmodule = NS2::Gtk::Lib
101
+ GirFFI::Builder.build_module 'Gtk', 'NS2'
102
+ assert_equal oldmodule, NS2::Gtk::Lib
103
+ end
104
+ end
105
+
106
+ context "looking at Gtk.main" do
107
+ setup do
108
+ @go = get_function_introspection_data 'Gtk', 'main'
109
+ end
110
+ # TODO: function_introspection_data should not return introspection data if not a function.
111
+ should "have correct introspection data" do
112
+ gir = GirFFI::IRepository.default
113
+ gir.require "Gtk", nil
114
+ go2 = gir.find_by_name "Gtk", "main"
115
+ assert_equal go2, @go
116
+ end
117
+
118
+ should "build correct definition of Gtk.main" do
119
+ code = GirFFI::Builder.send :function_definition, @go, Lib
120
+
121
+ expected = "def main\nLib.gtk_main\nend"
122
+
123
+ assert_equal cws(expected), cws(code)
124
+ end
125
+
126
+ should "attach function to Whatever::Lib" do
127
+ mod = Module.new
128
+ mod.const_set :Lib, libmod = Module.new
129
+ libmod.module_eval do
130
+ extend FFI::Library
131
+ ffi_lib "gtk-x11-2.0"
132
+ end
133
+
134
+ GirFFI::Builder.send :attach_ffi_function, mod, libmod, @go, nil
135
+ assert_contains libmod.public_methods.map(&:to_sym), :gtk_main
136
+ end
137
+ end
138
+
139
+ context "looking at Gtk.init" do
140
+ setup do
141
+ GirFFI::Builder.build_module 'Gtk'
142
+ @go = get_function_introspection_data 'Gtk', 'init'
143
+ end
144
+
145
+ should "delegate definition to FunctionDefinitionBuilder" do
146
+ code = GirFFI::Builder.send :function_definition, @go, Lib
147
+ expected = GirFFI::FunctionDefinitionBuilder.new(@go, Lib).generate
148
+ assert_equal cws(expected), cws(code)
149
+ end
150
+
151
+ should "have :pointer, :pointer as types of the arguments for the attached function" do
152
+ # FIXME: Ideally, we attach the function and test that it requires
153
+ # the correct argument types.
154
+ assert_equal [:pointer, :pointer], GirFFI::Builder.send(:ffi_function_argument_types, Gtk, Gtk::Lib, @go, nil)
155
+ end
156
+
157
+ should "have :void as return type for the attached function" do
158
+ assert_equal :void, GirFFI::Builder.send(:ffi_function_return_type, Gtk, Gtk::Lib, @go, nil)
159
+ end
160
+ end
161
+
162
+ context "looking at Gtk::Widget#show" do
163
+ setup do
164
+ @go = get_method_introspection_data 'Gtk', 'Widget', 'show'
165
+ end
166
+
167
+ should "delegate definition to FunctionDefinitionBuilder" do
168
+ code = GirFFI::Builder.send :function_definition, @go, Lib
169
+ expected = GirFFI::FunctionDefinitionBuilder.new(@go, Lib).generate
170
+ assert_equal cws(expected), cws(code)
171
+ end
172
+
173
+ should "have :pointer as types of the arguments for the attached function" do
174
+ assert_equal [:pointer], GirFFI::Builder.send(:ffi_function_argument_types, Gtk, Gtk::Lib, @go, nil)
175
+ end
176
+
177
+ end
178
+
179
+ context "looking at GObject.signal_connect_data" do
180
+ setup do
181
+ GirFFI::Builder.build_module 'GObject', 'NS5'
182
+ @go = get_function_introspection_data 'GObject', 'signal_connect_data'
183
+ end
184
+
185
+ should "delegate definition to FunctionDefinitionBuilder" do
186
+ code = GirFFI::Builder.send :function_definition, @go, Lib
187
+ expected = GirFFI::FunctionDefinitionBuilder.new(@go, Lib).generate
188
+ assert_equal cws(expected), cws(code)
189
+ end
190
+
191
+ should "have the correct types of the arguments for the attached function" do
192
+ assert_equal [:pointer, :string, :Callback, :pointer, :ClosureNotify, NS5::GObject::ConnectFlags],
193
+ GirFFI::Builder.send(:ffi_function_argument_types, NS5::GObject, NS5::GObject::Lib, @go, 'NS5')
194
+ end
195
+
196
+ should "define ffi callback types :Callback and :ClosureNotify" do
197
+ GirFFI::Builder.setup_function 'GObject', NS5::GObject::Lib, NS5::GObject, 'signal_connect_data'
198
+ cb = NS5::GObject::Lib.find_type :Callback
199
+ cn = NS5::GObject::Lib.find_type :ClosureNotify
200
+
201
+ assert_equal FFI.find_type(:void), cb.result_type
202
+ assert_equal FFI.find_type(:void), cn.result_type
203
+ assert_equal [], cb.param_types
204
+ assert_equal [FFI.find_type(:pointer), FFI.find_type(:pointer)], cn.param_types
205
+ end
206
+
207
+ should "define ffi enum type ConnectFlags" do
208
+ assert_equal({:after => 1, :swapped => 2}, NS5::GObject::ConnectFlags.to_h)
209
+ end
210
+ end
211
+
212
+ context "building Everything::TestStructA" do
213
+ setup do
214
+ GirFFI::Builder.build_class 'Everything', 'TestStructA'
215
+ end
216
+
217
+ should "set up the correct struct members" do
218
+ assert_equal [:some_int, :some_int8, :some_double, :some_enum],
219
+ Everything::TestStructA::Struct.members
220
+ end
221
+
222
+ should "set up struct members with the correct offset" do
223
+ info = GirFFI::IRepository.default.find_by_name 'Everything', 'TestStructA'
224
+ assert_equal info.fields.map{|f| [f.name.to_sym, f.offset]},
225
+ Everything::TestStructA::Struct.offsets
226
+ end
227
+
228
+ should "set up struct members with the correct types" do
229
+ tags = [:int, :int8, :double, Everything::TestEnum]
230
+ assert_equal tags.map {|t| FFI.find_type t},
231
+ Everything::TestStructA::Struct.layout.fields.map(&:type)
232
+ end
233
+ end
234
+
235
+ context "building Everything::TestBoxed" do
236
+ setup do
237
+ GirFFI::Builder.build_class 'Everything', 'TestBoxed'
238
+ end
239
+
240
+ should "set up #_real_new as an alias to #new" do
241
+ assert Everything::TestBoxed.respond_to? "_real_new"
242
+ end
243
+
244
+ should "allow creation using #new" do
245
+ tb = Everything::TestBoxed.new
246
+ assert_instance_of Everything::TestBoxed, tb
247
+ end
248
+
249
+ should "allow creation using alternative constructors" do
250
+ tb = Everything::TestBoxed.new_alternative_constructor1 1
251
+ assert_instance_of Everything::TestBoxed, tb
252
+ assert_equal 1, tb[:some_int8]
253
+
254
+ tb = Everything::TestBoxed.new_alternative_constructor2 1, 2
255
+ assert_instance_of Everything::TestBoxed, tb
256
+ assert_equal 1 + 2, tb[:some_int8]
257
+
258
+ tb = Everything::TestBoxed.new_alternative_constructor3 "54"
259
+ assert_instance_of Everything::TestBoxed, tb
260
+ assert_equal 54, tb[:some_int8]
261
+ end
262
+
263
+ should "make the equals method work" do
264
+ tb = Everything::TestBoxed.new_alternative_constructor1 123
265
+ tb2 = Everything::TestBoxed.new_alternative_constructor2 120, 3
266
+ assert_equal true, tb.equals(tb2)
267
+ end
268
+
269
+ should "make the copy method work" do
270
+ tb = Everything::TestBoxed.new_alternative_constructor1 123
271
+ tb2 = tb.copy
272
+ assert_instance_of Everything::TestBoxed, tb2
273
+ assert_equal 123, tb2[:some_int8], "fields copied"
274
+ tb2[:some_int8] = 89
275
+ assert_equal 123, tb[:some_int8], "is a true copy"
276
+ end
277
+ end
278
+
279
+ context "building Everything::TestEnum" do
280
+ setup do
281
+ GirFFI::Builder.build_class 'Everything', 'TestEnum'
282
+ end
283
+ should "create an object of type FFI::Enum" do
284
+ assert_instance_of FFI::Enum, Everything::TestEnum
285
+ end
286
+ end
287
+
288
+ # TODO: Should not allow functions to be called as methods, etc.
289
+
290
+ context "looking at Everything's functions" do
291
+ setup do
292
+ GirFFI::Builder.build_module 'Everything'
293
+ end
294
+
295
+ should "correctly handle test_boolean" do
296
+ assert_equal false, Everything.test_boolean(false)
297
+ assert_equal true, Everything.test_boolean(true)
298
+ end
299
+
300
+ should "correctly handle test_callback_user_data" do
301
+ a = :foo
302
+ result = Everything.test_callback_user_data Proc.new {|u|
303
+ a = u
304
+ 5
305
+ }, :bar
306
+ assert_equal :bar, a
307
+ assert_equal 5, result
308
+ end
309
+ end
310
+
311
+ context "building the Everything module" do
312
+ setup do
313
+ GirFFI::Builder.build_module 'Everything', 'NS4'
314
+ end
315
+
316
+ should "create a method_missing method for the module" do
317
+ ms = (NS4::Everything.public_methods - Module.public_methods).map(&:to_sym)
318
+ assert_contains ms, :method_missing
319
+ end
320
+
321
+ should "cause the TestObj class to be autocreated" do
322
+ assert !NS4::Everything.const_defined?(:TestObj)
323
+ assert_nothing_raised {NS4::Everything::TestObj}
324
+ assert NS4::Everything.const_defined? :TestObj
325
+ end
326
+ end
327
+ end
328
+ end