gir_ffi 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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