spotify 12.2.0 → 12.3.0
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.
- 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
|