spotify 12.2.0 → 12.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -1
- data/.rspec +5 -0
- data/CHANGELOG.md +45 -27
- data/Gemfile +4 -0
- data/README.markdown +52 -19
- data/Rakefile +16 -7
- data/lib/spotify.rb +109 -8
- data/lib/spotify/api.rb +61 -0
- data/lib/spotify/api/album.rb +14 -0
- data/lib/spotify/api/album_browse.rb +18 -0
- data/lib/spotify/api/artist.rb +10 -0
- data/lib/spotify/api/artist_browse.rb +23 -0
- data/lib/spotify/api/error.rb +6 -0
- data/lib/spotify/api/image.rb +16 -0
- data/lib/spotify/api/inbox.rb +9 -0
- data/lib/spotify/api/link.rb +25 -0
- data/lib/spotify/api/playlist.rb +39 -0
- data/lib/spotify/api/playlist_container.rb +23 -0
- data/lib/spotify/api/search.rb +27 -0
- data/lib/spotify/api/session.rb +46 -0
- data/lib/spotify/api/toplist_browse.rb +17 -0
- data/lib/spotify/api/track.rb +26 -0
- data/lib/spotify/api/user.rb +10 -0
- data/lib/spotify/defines.rb +109 -0
- data/lib/spotify/error.rb +62 -0
- data/lib/spotify/managed_pointer.rb +90 -0
- data/lib/spotify/objects.rb +16 -0
- data/lib/spotify/objects/album.rb +5 -0
- data/lib/spotify/objects/album_browse.rb +5 -0
- data/lib/spotify/objects/artist.rb +5 -0
- data/lib/spotify/objects/artist_browse.rb +5 -0
- data/lib/spotify/objects/image.rb +5 -0
- data/lib/spotify/objects/inbox.rb +5 -0
- data/lib/spotify/objects/link.rb +5 -0
- data/lib/spotify/objects/playlist.rb +5 -0
- data/lib/spotify/objects/playlist_container.rb +5 -0
- data/lib/spotify/objects/search.rb +5 -0
- data/lib/spotify/objects/session.rb +20 -0
- data/lib/spotify/objects/toplist_browse.rb +5 -0
- data/lib/spotify/objects/track.rb +5 -0
- data/lib/spotify/objects/user.rb +5 -0
- data/lib/spotify/structs.rb +46 -0
- data/lib/spotify/structs/audio_buffer_stats.rb +10 -0
- data/lib/spotify/structs/audio_format.rb +12 -0
- data/lib/spotify/structs/offline_sync_status.rb +24 -0
- data/lib/spotify/structs/playlist_callbacks.rb +32 -0
- data/lib/spotify/structs/playlist_container_callbacks.rb +14 -0
- data/lib/spotify/structs/session_callbacks.rb +48 -0
- data/lib/spotify/structs/session_config.rb +64 -0
- data/lib/spotify/structs/subscribers.rb +31 -0
- data/lib/spotify/types.rb +3 -0
- data/lib/spotify/types/image_id.rb +5 -0
- data/lib/spotify/types/nul_string.rb +51 -0
- data/lib/spotify/types/utf8_string.rb +24 -28
- data/lib/spotify/util.rb +36 -0
- data/lib/spotify/version.rb +7 -2
- data/spec/api-linux.xml +1887 -0
- data/spec/api-mac.xml +1886 -0
- data/spec/spec_helper.rb +20 -0
- data/spec/spotify/api_spec.rb +62 -0
- data/spec/spotify/defines_spec.rb +22 -0
- data/spec/spotify/enums_spec.rb +9 -0
- data/spec/spotify/managed_pointer_spec.rb +75 -0
- data/spec/spotify/spotify_spec.rb +69 -0
- data/spec/spotify/structs/session_config_spec.rb +20 -0
- data/spec/spotify/structs/struct_spec.rb +34 -0
- data/spec/spotify/structs/subscribers_spec.rb +31 -0
- data/spec/spotify/structs_spec.rb +19 -0
- data/spec/spotify/types/image_id_spec.rb +44 -0
- data/spec/spotify/types/nul_string_spec.rb +31 -0
- data/spec/spotify/types/utf8_string_spec.rb +24 -0
- data/spec/support/hook_spotify.rb +37 -0
- data/spec/support/spotify_util.rb +17 -0
- data/spotify.gemspec +6 -11
- metadata +99 -26
- data/lib/spotify/error_wrappers.rb +0 -165
- data/lib/spotify/functions.rb +0 -755
- data/lib/spotify/gc_wrappers.rb +0 -105
- data/lib/spotify/types/pointer.rb +0 -59
- data/spec/spotify_spec.rb +0 -467
data/lib/spotify/gc_wrappers.rb
DELETED
@@ -1,105 +0,0 @@
|
|
1
|
-
# This file contains a tiny (!) DSL that wraps existing Spotify
|
2
|
-
# functions into versions that return Spotify::Pointers instead
|
3
|
-
# of the usual FFI::Pointers. The Spotify::Pointer automatically
|
4
|
-
# manages the underlying pointers reference count, which allows
|
5
|
-
# us to piggyback on the Ruby GC mechanism.
|
6
|
-
|
7
|
-
module Spotify
|
8
|
-
# Wraps the function `function` so that it always returns
|
9
|
-
# a Spotify::Pointer with correct refcount. Functions that
|
10
|
-
# contain the word `create` are assumed to start out with
|
11
|
-
# a refcount of `+1`.
|
12
|
-
#
|
13
|
-
# @note This method is removed at the bottom of this file.
|
14
|
-
#
|
15
|
-
# @param [#to_s] function
|
16
|
-
# @param [#to_s] return_type
|
17
|
-
# @raise [NoMethodError] if `function` is not defined
|
18
|
-
# @see Spotify::Pointer
|
19
|
-
def self.wrap_function(function, return_type)
|
20
|
-
method(function) # make sure it exists
|
21
|
-
define_singleton_method("#{function}!") do |*args, &block|
|
22
|
-
pointer = public_send(function, *args, &block)
|
23
|
-
Spotify::Pointer.new(pointer, return_type, function !~ /create/)
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
# @macro [attach] wrap_function
|
28
|
-
# Same as {Spotify}.`$1`, but wraps result in a {Spotify::Pointer}.
|
29
|
-
#
|
30
|
-
# @method $1!
|
31
|
-
# @return [Spotify::Pointer<$2>]
|
32
|
-
# @see #$1
|
33
|
-
wrap_function :session_user, :user
|
34
|
-
wrap_function :session_playlistcontainer, :playlistcontainer
|
35
|
-
wrap_function :session_inbox_create, :playlist
|
36
|
-
wrap_function :session_starred_create, :playlist
|
37
|
-
wrap_function :session_starred_for_user_create, :playlist
|
38
|
-
wrap_function :session_publishedcontainer_for_user_create, :playlistcontainer
|
39
|
-
|
40
|
-
wrap_function :track_artist, :artist
|
41
|
-
wrap_function :track_album, :album
|
42
|
-
wrap_function :localtrack_create, :track
|
43
|
-
wrap_function :track_get_playable, :track
|
44
|
-
|
45
|
-
wrap_function :album_artist, :artist
|
46
|
-
|
47
|
-
wrap_function :albumbrowse_create, :albumbrowse
|
48
|
-
wrap_function :albumbrowse_album, :album
|
49
|
-
wrap_function :albumbrowse_artist, :artist
|
50
|
-
wrap_function :albumbrowse_track, :track
|
51
|
-
|
52
|
-
wrap_function :artistbrowse_create, :artistbrowse
|
53
|
-
wrap_function :artistbrowse_artist, :artist
|
54
|
-
wrap_function :artistbrowse_track, :track
|
55
|
-
wrap_function :artistbrowse_album, :album
|
56
|
-
wrap_function :artistbrowse_similar_artist, :artist
|
57
|
-
wrap_function :artistbrowse_tophit_track, :track
|
58
|
-
|
59
|
-
wrap_function :image_create, :image
|
60
|
-
wrap_function :image_create_from_link, :image
|
61
|
-
|
62
|
-
wrap_function :link_as_track, :track
|
63
|
-
wrap_function :link_as_track_and_offset, :track
|
64
|
-
wrap_function :link_as_album, :album
|
65
|
-
wrap_function :link_as_artist, :artist
|
66
|
-
wrap_function :link_as_user, :user
|
67
|
-
|
68
|
-
wrap_function :link_create_from_string, :link
|
69
|
-
wrap_function :link_create_from_track, :link
|
70
|
-
wrap_function :link_create_from_album, :link
|
71
|
-
wrap_function :link_create_from_artist, :link
|
72
|
-
wrap_function :link_create_from_search, :link
|
73
|
-
wrap_function :link_create_from_playlist, :link
|
74
|
-
wrap_function :link_create_from_artist_portrait, :link
|
75
|
-
wrap_function :link_create_from_artistbrowse_portrait, :link
|
76
|
-
wrap_function :link_create_from_album_cover, :link
|
77
|
-
wrap_function :link_create_from_image, :link
|
78
|
-
wrap_function :link_create_from_user, :link
|
79
|
-
|
80
|
-
wrap_function :search_create, :search
|
81
|
-
wrap_function :search_track, :track
|
82
|
-
wrap_function :search_album, :album
|
83
|
-
wrap_function :search_artist, :artist
|
84
|
-
wrap_function :search_playlist, :playlist
|
85
|
-
|
86
|
-
wrap_function :playlist_track, :track
|
87
|
-
wrap_function :playlist_track_creator, :user
|
88
|
-
wrap_function :playlist_owner, :user
|
89
|
-
wrap_function :playlist_create, :playlist
|
90
|
-
|
91
|
-
wrap_function :playlistcontainer_playlist, :playlist
|
92
|
-
wrap_function :playlistcontainer_add_new_playlist, :playlist
|
93
|
-
wrap_function :playlistcontainer_add_playlist, :playlist
|
94
|
-
wrap_function :playlistcontainer_owner, :user
|
95
|
-
|
96
|
-
wrap_function :toplistbrowse_create, :toplistbrowse
|
97
|
-
wrap_function :toplistbrowse_artist, :artist
|
98
|
-
wrap_function :toplistbrowse_album, :album
|
99
|
-
wrap_function :toplistbrowse_track, :track
|
100
|
-
|
101
|
-
wrap_function :inbox_post_tracks, :inbox
|
102
|
-
|
103
|
-
# Clean up
|
104
|
-
class << self; undef :wrap_function; end
|
105
|
-
end
|
@@ -1,59 +0,0 @@
|
|
1
|
-
module Spotify
|
2
|
-
# The Pointer is a kind of AutoPointer specially tailored for Spotify
|
3
|
-
# objects, that releases the raw pointer on GC.
|
4
|
-
class Pointer < FFI::AutoPointer
|
5
|
-
# Raised when #releaser_for is given an invalid type.
|
6
|
-
class InvalidTypeError < StandardError
|
7
|
-
end
|
8
|
-
|
9
|
-
class << self
|
10
|
-
# Create a proc that will accept a pointer of a given type and
|
11
|
-
# release it with the correct function if it’s not null.
|
12
|
-
#
|
13
|
-
# @raise [InvalidTypeError] when given an invalid type
|
14
|
-
# @param [Symbol] type
|
15
|
-
# @return [Proc]
|
16
|
-
def releaser_for(type)
|
17
|
-
unless Spotify.respond_to?(:"#{type}_release!")
|
18
|
-
raise InvalidTypeError, "#{type} is not a valid Spotify type"
|
19
|
-
end
|
20
|
-
|
21
|
-
lambda do |pointer|
|
22
|
-
$stdout.puts "Spotify::#{type}_release!(#{pointer})" if $DEBUG
|
23
|
-
Spotify.send(:"#{type}_release!", pointer) unless pointer.null?
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
# Checks an object by pointer kind and type.
|
28
|
-
#
|
29
|
-
# @param [Object] object
|
30
|
-
# @param [Symbol] type
|
31
|
-
# @return [Boolean] true if object is a spotify pointer and of correct type
|
32
|
-
def typechecks?(object, type)
|
33
|
-
object.is_a?(Spotify::Pointer) && (object.type == type.to_s)
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
# @return [Symbol] type
|
38
|
-
attr_reader :type
|
39
|
-
|
40
|
-
# Initialize a Spotify pointer, which will automatically decrease
|
41
|
-
# the reference count of it’s pointer when garbage collected.
|
42
|
-
#
|
43
|
-
# @param [FFI::Pointer] pointer
|
44
|
-
# @param [#to_s] type session, link, etc
|
45
|
-
# @param [Boolean] add_ref will increase refcount by one if true
|
46
|
-
def initialize(pointer, type, add_ref)
|
47
|
-
super pointer, self.class.releaser_for(@type = type.to_s)
|
48
|
-
|
49
|
-
unless pointer.null?
|
50
|
-
Spotify.send(:"#{type}_add_ref!", pointer)
|
51
|
-
end if add_ref
|
52
|
-
end
|
53
|
-
|
54
|
-
# @return [String] representation of the spotify pointer
|
55
|
-
def to_s
|
56
|
-
"<#{self.class} address=0x#{address.to_s(16)} type=#{type}>"
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
data/spec/spotify_spec.rb
DELETED
@@ -1,467 +0,0 @@
|
|
1
|
-
# coding: utf-8
|
2
|
-
require 'rubygems' # needed for 1.8, does not matter in 1.9
|
3
|
-
|
4
|
-
require 'ostruct'
|
5
|
-
require 'set'
|
6
|
-
require 'rbgccxml'
|
7
|
-
require 'minitest/mock'
|
8
|
-
require 'minitest/autorun'
|
9
|
-
|
10
|
-
#
|
11
|
-
# Hooking FFI for extra introspection
|
12
|
-
#
|
13
|
-
require 'ffi'
|
14
|
-
|
15
|
-
module Spotify
|
16
|
-
extend FFI::Library
|
17
|
-
extend self
|
18
|
-
|
19
|
-
def attach_function(name, func, arguments, returns = nil, options = nil)
|
20
|
-
args = [name, func, arguments, returns, options].compact
|
21
|
-
args.unshift name.to_s if func.is_a?(Array)
|
22
|
-
|
23
|
-
hargs = [:name, :func, :args, :returns].zip args
|
24
|
-
@attached_methods ||= {}
|
25
|
-
@attached_methods[name.to_s] = hash = Hash[hargs]
|
26
|
-
|
27
|
-
super
|
28
|
-
end
|
29
|
-
|
30
|
-
def resolve_type(type)
|
31
|
-
type = find_type(type)
|
32
|
-
type = type.type if type.respond_to?(:type)
|
33
|
-
type
|
34
|
-
end
|
35
|
-
|
36
|
-
attr_reader :attached_methods
|
37
|
-
|
38
|
-
RUBY_PLATFORM = ENV.fetch('RUBY_PLATFORM') do
|
39
|
-
puts "[WARN] Tests running with default ruby platform, #{::RUBY_PLATFORM}, please be"
|
40
|
-
puts "[WARN] specific in which platform to target by setting ENV[RUBY_PLATFORM]"
|
41
|
-
puts "(warnings coming from #{__FILE__}:#{__LINE__})"
|
42
|
-
puts
|
43
|
-
::RUBY_PLATFORM
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
require 'spotify'
|
48
|
-
|
49
|
-
module C
|
50
|
-
extend FFI::Library
|
51
|
-
ffi_lib 'C'
|
52
|
-
|
53
|
-
typedef Spotify::UTF8String, :utf8_string
|
54
|
-
|
55
|
-
attach_function :strncpy, [ :pointer, :utf8_string, :size_t ], :utf8_string
|
56
|
-
end
|
57
|
-
|
58
|
-
# Used for checking Spotify::Pointer things.
|
59
|
-
module Spotify
|
60
|
-
def bogus_add_ref!(pointer)
|
61
|
-
end
|
62
|
-
|
63
|
-
def bogus_release!(pointer)
|
64
|
-
end
|
65
|
-
|
66
|
-
# This may be called after our GC test. Randomly.
|
67
|
-
def garbage_release!(pointer)
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
#
|
72
|
-
# Utility
|
73
|
-
#
|
74
|
-
|
75
|
-
API_H_PATH = File.expand_path("../api-#{Spotify.platform}.h", __FILE__)
|
76
|
-
API_H_SRC = File.read(API_H_PATH)
|
77
|
-
API_H_XML = RbGCCXML.parse(API_H_PATH)
|
78
|
-
|
79
|
-
#
|
80
|
-
# General
|
81
|
-
#
|
82
|
-
describe Spotify do
|
83
|
-
describe "VERSION" do
|
84
|
-
it "should be defined" do
|
85
|
-
defined?(Spotify::VERSION).must_equal "constant"
|
86
|
-
end
|
87
|
-
|
88
|
-
it "should be the same version as in api.h" do
|
89
|
-
spotify_version = API_H_SRC.match(/#define\s+SPOTIFY_API_VERSION\s+(\d+)/)[1]
|
90
|
-
Spotify::API_VERSION.must_equal spotify_version.to_i
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
describe ".enum_value!" do
|
95
|
-
it "raises an error if given an invalid enum value" do
|
96
|
-
proc { Spotify.enum_value!(:moo, "error value") }.must_raise(ArgumentError)
|
97
|
-
end
|
98
|
-
|
99
|
-
it "gives back the enum value for that enum" do
|
100
|
-
Spotify.enum_value!(:ok, "error value").must_equal 0
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
104
|
-
describe Spotify::SessionConfig do
|
105
|
-
it "allows setting boolean values with bools" do
|
106
|
-
subject = Spotify::SessionConfig.new
|
107
|
-
|
108
|
-
subject[:compress_playlists].must_equal false
|
109
|
-
subject[:dont_save_metadata_for_playlists].must_equal false
|
110
|
-
subject[:initially_unload_playlists].must_equal false
|
111
|
-
|
112
|
-
subject[:compress_playlists] = true
|
113
|
-
subject[:dont_save_metadata_for_playlists] = true
|
114
|
-
subject[:initially_unload_playlists] = true
|
115
|
-
|
116
|
-
subject[:compress_playlists].must_equal true
|
117
|
-
subject[:dont_save_metadata_for_playlists].must_equal true
|
118
|
-
subject[:initially_unload_playlists].must_equal true
|
119
|
-
end
|
120
|
-
|
121
|
-
it "should be possible to set the callbacks" do
|
122
|
-
subject = Spotify::SessionConfig.new
|
123
|
-
subject[:callbacks] = Spotify::SessionCallbacks.new
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
|
-
describe Spotify::OfflineSyncStatus do
|
128
|
-
it "allows setting boolean values with bools" do
|
129
|
-
subject = Spotify::OfflineSyncStatus.new
|
130
|
-
|
131
|
-
subject[:syncing].must_equal false
|
132
|
-
subject[:syncing] = true
|
133
|
-
subject[:syncing].must_equal true
|
134
|
-
end
|
135
|
-
end
|
136
|
-
|
137
|
-
describe "audio sample types" do
|
138
|
-
# this is so we can just read audio frames easily based on the sample type
|
139
|
-
Spotify.enum_type(:sampletype).symbols.each do |type|
|
140
|
-
describe type do
|
141
|
-
it "should have a corresponding FFI::Pointer#read_array_of_#{type}" do
|
142
|
-
FFI::Pointer.new(1).must_respond_to "read_array_of_#{type}"
|
143
|
-
end
|
144
|
-
end
|
145
|
-
end
|
146
|
-
end
|
147
|
-
|
148
|
-
describe "error wrapped functions" do
|
149
|
-
wrapped_methods = Spotify.attached_methods.find_all { |meth, info| info[:returns] == :error }
|
150
|
-
wrapped_methods.each do |meth, info|
|
151
|
-
it "raises an error if #{meth}! returns non-ok" do
|
152
|
-
Spotify.stub(meth, :bad_application_key) do
|
153
|
-
proc { Spotify.send("#{meth}!") }.must_raise(Spotify::Error, /BAD_APPLICATION_KEY/)
|
154
|
-
end
|
155
|
-
end
|
156
|
-
end
|
157
|
-
end
|
158
|
-
|
159
|
-
describe "GC wrapped functions" do
|
160
|
-
gc_types = Set.new([:session, :track, :user, :playlistcontainer, :playlist, :link, :album, :artist, :search, :image, :albumbrowse, :artistbrowse, :toplistbrowse, :inbox])
|
161
|
-
wrapped_methods = Spotify.attached_methods.find_all { |meth, info| gc_types.member?(info[:returns]) }
|
162
|
-
wrapped_methods.each do |meth, info|
|
163
|
-
it "returns a Spotify::Pointer for #{meth}!" do
|
164
|
-
Spotify.stub(meth, lambda { FFI::Pointer.new(0) }) do
|
165
|
-
Spotify.send("#{meth}!").must_be_instance_of Spotify::Pointer
|
166
|
-
end
|
167
|
-
end
|
168
|
-
end
|
169
|
-
|
170
|
-
it "adds a ref to the pointer if required" do
|
171
|
-
session = FFI::Pointer.new(1)
|
172
|
-
ref_added = false
|
173
|
-
|
174
|
-
Spotify.stub(:session_user, FFI::Pointer.new(1)) do
|
175
|
-
Spotify.stub(:user_add_ref, proc { ref_added = true; :ok }) do
|
176
|
-
Spotify.session_user!(session)
|
177
|
-
end
|
178
|
-
end
|
179
|
-
|
180
|
-
ref_added.must_equal true
|
181
|
-
end
|
182
|
-
|
183
|
-
it "does not add a ref when the result is null" do
|
184
|
-
session = FFI::Pointer.new(1)
|
185
|
-
ref_added = false
|
186
|
-
|
187
|
-
Spotify.stub(:session_user, FFI::Pointer.new(0)) do
|
188
|
-
Spotify.stub(:user_add_ref, proc { ref_added = true }) do
|
189
|
-
Spotify.session_user!(session)
|
190
|
-
end
|
191
|
-
end
|
192
|
-
|
193
|
-
ref_added.must_equal false
|
194
|
-
end
|
195
|
-
|
196
|
-
it "does not add a ref when the result already has one" do
|
197
|
-
session = FFI::Pointer.new(1)
|
198
|
-
ref_added = false
|
199
|
-
|
200
|
-
Spotify.stub(:albumbrowse_create, FFI::Pointer.new(1)) do
|
201
|
-
Spotify.stub(:albumbrowse_add_ref, proc { ref_added = true }) do
|
202
|
-
# to avoid it trying to GC our fake pointer later, and cause a
|
203
|
-
# segfault in our tests
|
204
|
-
Spotify::Pointer.stub(:releaser_for, proc { proc {} }) do
|
205
|
-
Spotify.albumbrowse_create!(session)
|
206
|
-
end
|
207
|
-
end
|
208
|
-
end
|
209
|
-
|
210
|
-
ref_added.must_equal false
|
211
|
-
end
|
212
|
-
end
|
213
|
-
|
214
|
-
describe Spotify::Pointer do
|
215
|
-
describe ".new" do
|
216
|
-
it "adds a reference on the given pointer" do
|
217
|
-
ref_added = false
|
218
|
-
|
219
|
-
Spotify.stub(:bogus_add_ref!, proc { ref_added = true; :ok }) do
|
220
|
-
Spotify::Pointer.new(FFI::Pointer.new(1), :bogus, true)
|
221
|
-
end
|
222
|
-
|
223
|
-
ref_added.must_equal true
|
224
|
-
end
|
225
|
-
|
226
|
-
it "does not add a reference on the given pointer if it is NULL" do
|
227
|
-
ref_added = false
|
228
|
-
|
229
|
-
Spotify.stub(:bogus_add_ref!, proc { ref_added = true; :ok }) do
|
230
|
-
Spotify::Pointer.new(FFI::Pointer::NULL, :bogus, true)
|
231
|
-
end
|
232
|
-
|
233
|
-
ref_added.must_equal false
|
234
|
-
end
|
235
|
-
|
236
|
-
it "raises an error when given an invalid type" do
|
237
|
-
proc { Spotify::Pointer.new(FFI::Pointer.new(1), :really_bogus, true) }.
|
238
|
-
must_raise(Spotify::Pointer::InvalidTypeError, /invalid/)
|
239
|
-
end
|
240
|
-
end
|
241
|
-
|
242
|
-
describe ".typechecks?" do
|
243
|
-
it "typechecks a given spotify pointer" do
|
244
|
-
pointer = Spotify::Pointer.new(FFI::Pointer.new(1), :bogus, true)
|
245
|
-
bogus = OpenStruct.new(:type => :bogus)
|
246
|
-
Spotify::Pointer.typechecks?(bogus, :bogus).must_equal false
|
247
|
-
Spotify::Pointer.typechecks?(pointer, :link).must_equal false
|
248
|
-
Spotify::Pointer.typechecks?(pointer, :bogus).must_equal true
|
249
|
-
end
|
250
|
-
end
|
251
|
-
|
252
|
-
describe "garbage collection" do
|
253
|
-
let(:my_pointer) { FFI::Pointer.new(1) }
|
254
|
-
|
255
|
-
it "should work" do
|
256
|
-
gc_count = 0
|
257
|
-
|
258
|
-
Spotify.stub(:garbage_release!, proc { gc_count += 1 }) do
|
259
|
-
5.times { Spotify::Pointer.new(my_pointer, :garbage, false) }
|
260
|
-
5.times { GC.start; sleep 0.01 }
|
261
|
-
end
|
262
|
-
|
263
|
-
# GC tests are a bit funky, but as long as we garbage_release at least once, then
|
264
|
-
# we can assume our GC works properly, but up the stakes just for the sake of it
|
265
|
-
gc_count.must_be :>, 3
|
266
|
-
end
|
267
|
-
end
|
268
|
-
end
|
269
|
-
|
270
|
-
describe Spotify::UTF8String do
|
271
|
-
let(:char) do
|
272
|
-
char = "\xC4"
|
273
|
-
char.force_encoding('ISO-8859-1') if char.respond_to?(:force_encoding)
|
274
|
-
char
|
275
|
-
end
|
276
|
-
|
277
|
-
it "should convert any strings to UTF-8 before reading and writing" do
|
278
|
-
dest = FFI::MemoryPointer.new(:char, 3) # two bytes for the ä, one for the NULL
|
279
|
-
result = C.strncpy(dest, char, 3)
|
280
|
-
|
281
|
-
result.encoding.must_equal Encoding::UTF_8
|
282
|
-
result.must_equal "Ä"
|
283
|
-
result.bytesize.must_equal 2
|
284
|
-
end if "".respond_to?(:force_encoding)
|
285
|
-
|
286
|
-
it "should do nothing if strings does not respond to #encode or #force_encoding" do
|
287
|
-
dest = FFI::MemoryPointer.new(:char, 3) # two bytes for the ä, one for the NULL
|
288
|
-
result = C.strncpy(dest, char, 3)
|
289
|
-
|
290
|
-
result.must_equal "\xC4"
|
291
|
-
result.bytesize.must_equal 1
|
292
|
-
end unless "".respond_to?(:force_encoding)
|
293
|
-
end
|
294
|
-
|
295
|
-
describe Spotify::ImageID do
|
296
|
-
let(:context) { nil }
|
297
|
-
let(:subject) { Spotify.find_type(:image_id) }
|
298
|
-
let(:null_pointer) { FFI::Pointer::NULL }
|
299
|
-
|
300
|
-
let(:image_id_pointer) do
|
301
|
-
pointer = FFI::MemoryPointer.new(:char, 20)
|
302
|
-
pointer.write_string(image_id)
|
303
|
-
pointer
|
304
|
-
end
|
305
|
-
|
306
|
-
let(:image_id) do
|
307
|
-
# deliberate NULL in middle of string
|
308
|
-
image_id = ":\xD94#\xAD\xD9\x97f\xE0\x00V6\x05\xC6\xE7n\xD2\xB0\xE4P"
|
309
|
-
image_id.force_encoding("BINARY") if image_id.respond_to?(:force_encoding)
|
310
|
-
image_id
|
311
|
-
end
|
312
|
-
|
313
|
-
describe "from_native" do
|
314
|
-
it "should be nil given a null pointer" do
|
315
|
-
subject.from_native(null_pointer, context).must_be_nil
|
316
|
-
end
|
317
|
-
|
318
|
-
it "should be an image id given a non-null pointer" do
|
319
|
-
subject.from_native(image_id_pointer, context).must_equal image_id
|
320
|
-
end
|
321
|
-
end
|
322
|
-
|
323
|
-
describe "to_native" do
|
324
|
-
it "should be a null pointer given nil" do
|
325
|
-
subject.to_native(nil, context).must_be_nil
|
326
|
-
end
|
327
|
-
|
328
|
-
it "should be a 20-byte C string given an actual string" do
|
329
|
-
pointer = subject.to_native(image_id, context)
|
330
|
-
pointer.read_string(20).must_equal image_id_pointer.read_string(20)
|
331
|
-
end
|
332
|
-
|
333
|
-
it "should raise an error given more or less than a 20 byte string" do
|
334
|
-
proc { subject.to_native(image_id + image_id, context) }.must_raise ArgumentError
|
335
|
-
proc { subject.to_native(image_id[0..10], context) }.must_raise ArgumentError
|
336
|
-
end
|
337
|
-
end
|
338
|
-
end
|
339
|
-
end
|
340
|
-
|
341
|
-
describe "functions" do
|
342
|
-
API_H_XML.functions.each do |func|
|
343
|
-
next unless func["name"] =~ /\Asp_/
|
344
|
-
attached_name = func["name"].sub(/\Asp_/, '')
|
345
|
-
|
346
|
-
def type_of(type, return_type = false)
|
347
|
-
return case type.to_cpp
|
348
|
-
when "const char*"
|
349
|
-
:utf8_string
|
350
|
-
when /\A(::)?(char|int|size_t|bool|sp_scrobbling_state|sp_session\*|byte)\*/
|
351
|
-
return_type ? :pointer : :buffer_out
|
352
|
-
when /::(.+_cb)\*/
|
353
|
-
$1.to_sym
|
354
|
-
else :pointer
|
355
|
-
end if type.is_a?(RbGCCXML::PointerType)
|
356
|
-
|
357
|
-
case type["name"]
|
358
|
-
when "unsigned int"
|
359
|
-
:uint
|
360
|
-
else
|
361
|
-
type["name"].sub(/\Asp_/, '').to_sym
|
362
|
-
end
|
363
|
-
end
|
364
|
-
|
365
|
-
describe func["name"] do
|
366
|
-
it "should be attached" do
|
367
|
-
Spotify.must_respond_to attached_name
|
368
|
-
end
|
369
|
-
|
370
|
-
it "should expect the correct number of arguments" do
|
371
|
-
Spotify.attached_methods[attached_name][:args].count.
|
372
|
-
must_equal func.arguments.count
|
373
|
-
end
|
374
|
-
|
375
|
-
it "should return the correct type" do
|
376
|
-
current = Spotify.attached_methods[attached_name][:returns]
|
377
|
-
actual = type_of(func.return_type, true)
|
378
|
-
|
379
|
-
Spotify.resolve_type(current).must_equal Spotify.resolve_type(actual)
|
380
|
-
end
|
381
|
-
|
382
|
-
it "should expect the correct types of arguments" do
|
383
|
-
current = Spotify.attached_methods[attached_name][:args]
|
384
|
-
actual = func.arguments.map { |arg| type_of(arg.cpp_type) }
|
385
|
-
|
386
|
-
current = current.map { |x| Spotify.resolve_type(x) }
|
387
|
-
actual = actual.map { |x| Spotify.resolve_type(x) }
|
388
|
-
|
389
|
-
current.must_equal actual
|
390
|
-
end
|
391
|
-
end
|
392
|
-
end
|
393
|
-
end
|
394
|
-
|
395
|
-
describe "enums" do
|
396
|
-
API_H_XML.enumerations.each do |enum|
|
397
|
-
attached_enum = Spotify.enum_type enum["name"].sub(/\Asp_/, '').to_sym
|
398
|
-
original_enum = enum.values.map { |v| [v["name"].downcase, v["init"]] }
|
399
|
-
|
400
|
-
describe enum["name"] do
|
401
|
-
it "should exist" do
|
402
|
-
attached_enum.wont_be_nil
|
403
|
-
end
|
404
|
-
|
405
|
-
it "should match the definition" do
|
406
|
-
attached_enum_map = attached_enum.symbol_map
|
407
|
-
original_enum.each do |(name, value)|
|
408
|
-
a_name, a_value = attached_enum_map.max_by { |(n, v)| (n.to_s.length if name.match(n.to_s)).to_i }
|
409
|
-
attached_enum_map.delete(a_name)
|
410
|
-
a_value.to_s.must_equal value
|
411
|
-
end
|
412
|
-
end
|
413
|
-
end
|
414
|
-
end
|
415
|
-
end
|
416
|
-
|
417
|
-
describe "structs" do
|
418
|
-
API_H_XML.structs.each do |struct|
|
419
|
-
next if struct["incomplete"]
|
420
|
-
|
421
|
-
attached_struct = Spotify.constants.find do |const|
|
422
|
-
struct["name"].gsub('_', '').match(/#{const}/i)
|
423
|
-
end
|
424
|
-
|
425
|
-
attached_members = Spotify.const_get(attached_struct).members.map(&:to_s)
|
426
|
-
|
427
|
-
describe struct["name"] do
|
428
|
-
it "should contain the same attributes" do
|
429
|
-
struct.variables.map(&:name).each_with_index do |member, index|
|
430
|
-
attached_members[index].must_equal member
|
431
|
-
end
|
432
|
-
end
|
433
|
-
end
|
434
|
-
end
|
435
|
-
|
436
|
-
describe Spotify::Subscribers do
|
437
|
-
it "should create the subscribers array using count" do
|
438
|
-
# Memory looks like this:
|
439
|
-
#
|
440
|
-
# 00 00 00 00 <- count of subscribers
|
441
|
-
# 00 00 00 00 <- pointer to subscriber 1
|
442
|
-
# …… …… …… ……
|
443
|
-
# 00 00 00 00 <- pointer to subscriber n
|
444
|
-
real_struct = FFI::MemoryPointer.new(:char, 24)
|
445
|
-
real_struct.put_uint(0, 2)
|
446
|
-
subscribers = %w[a bb].map { |x| FFI::MemoryPointer.from_string(x) }
|
447
|
-
real_struct.put_array_of_pointer(8, subscribers)
|
448
|
-
|
449
|
-
struct = Spotify::Subscribers.new(real_struct)
|
450
|
-
struct[:count].must_equal 2
|
451
|
-
struct[:subscribers].size.must_equal 2
|
452
|
-
struct[:subscribers][0].read_string.must_equal "a"
|
453
|
-
struct[:subscribers][1].read_string.must_equal "bb"
|
454
|
-
proc { struct[:subscribers][2] }.must_raise IndexError
|
455
|
-
end
|
456
|
-
|
457
|
-
|
458
|
-
it "should not fail given an empty subscribers struct" do
|
459
|
-
subscribers = FFI::MemoryPointer.new(:uint)
|
460
|
-
subscribers.write_uint(0)
|
461
|
-
|
462
|
-
subject = Spotify::Subscribers.new(subscribers)
|
463
|
-
subject[:count].must_equal 0
|
464
|
-
proc { subject[:subscribers] }.must_raise ArgumentError
|
465
|
-
end
|
466
|
-
end
|
467
|
-
end
|