aboutsummaryrefslogtreecommitdiffstats
path: root/lib/tools
diff options
context:
space:
mode:
Diffstat (limited to 'lib/tools')
-rw-r--r--lib/tools/doc/src/erlang_mode.xml2
-rw-r--r--lib/tools/doc/src/erlang_mode_chapter.xml6
-rw-r--r--lib/tools/doc/src/notes.xml15
-rw-r--r--lib/tools/doc/src/notes_history.xml30
-rw-r--r--lib/tools/doc/src/xref.xml2
-rw-r--r--lib/tools/emacs/erlang.el117
-rw-r--r--lib/tools/emacs/test.erl.indented56
-rw-r--r--lib/tools/emacs/test.erl.orig50
-rw-r--r--lib/tools/src/cover.erl4
-rw-r--r--lib/tools/test/Makefile90
-rw-r--r--lib/tools/test/cover_SUITE.erl1198
-rw-r--r--lib/tools/test/cover_SUITE_data/a.erl55
-rw-r--r--lib/tools/test/cover_SUITE_data/b.erl14
-rw-r--r--lib/tools/test/cover_SUITE_data/cc.erl88
-rw-r--r--lib/tools/test/cover_SUITE_data/compile_beam/crypt.erl6
-rw-r--r--lib/tools/test/cover_SUITE_data/compile_beam/d/y.erl6
-rw-r--r--lib/tools/test/cover_SUITE_data/compile_beam/v.erl6
-rw-r--r--lib/tools/test/cover_SUITE_data/compile_beam/w.erl6
-rw-r--r--lib/tools/test/cover_SUITE_data/compile_beam/x.erl6
-rw-r--r--lib/tools/test/cover_SUITE_data/d.erl156
-rw-r--r--lib/tools/test/cover_SUITE_data/d1/e.erl127
-rw-r--r--lib/tools/test/cover_SUITE_data/f.erl10
-rw-r--r--lib/tools/test/cover_SUITE_data/included_functions/cover_inc.erl8
-rw-r--r--lib/tools/test/cover_SUITE_data/included_functions/cover_inc.hrl7
-rw-r--r--lib/tools/test/cover_SUITE_data/otp_6115/f1.erl12
-rw-r--r--lib/tools/test/cover_SUITE_data/otp_6115/f2.erl13
-rw-r--r--lib/tools/test/cprof_SUITE.erl309
-rw-r--r--lib/tools/test/cprof_SUITE_data/cprof_SUITE_test.erl25
-rw-r--r--lib/tools/test/emem_SUITE.erl713
-rw-r--r--lib/tools/test/eprof_SUITE.erl97
-rw-r--r--lib/tools/test/eprof_SUITE_data/ed.script8
-rw-r--r--lib/tools/test/eprof_SUITE_data/eed.erl815
-rw-r--r--lib/tools/test/eprof_SUITE_data/eprof_suite_test.erl74
-rw-r--r--lib/tools/test/fprof_SUITE.erl1191
-rw-r--r--lib/tools/test/fprof_SUITE_data/foo.erl41
l---------lib/tools/test/ignore_cores.erl1
-rw-r--r--lib/tools/test/instrument_SUITE.erl129
-rw-r--r--lib/tools/test/make_SUITE.erl295
-rw-r--r--lib/tools/test/make_SUITE_data/Emakefile20
-rw-r--r--lib/tools/test/make_SUITE_data/test1.erl10
-rw-r--r--lib/tools/test/make_SUITE_data/test2.erl10
-rw-r--r--lib/tools/test/make_SUITE_data/test3.erl10
-rw-r--r--lib/tools/test/make_SUITE_data/test4.erl10
-rw-r--r--lib/tools/test/make_SUITE_data/test5.erl10
-rw-r--r--lib/tools/test/tools.spec1
-rw-r--r--lib/tools/test/tools.spec.win2
-rw-r--r--lib/tools/test/tools_SUITE.erl56
-rw-r--r--lib/tools/test/xref_SUITE.erl2743
-rw-r--r--lib/tools/test/xref_SUITE_data/depr_r9c.beambin0 -> 1664 bytes
-rw-r--r--lib/tools/test/xref_SUITE_data/dir/dir/dummy2
-rw-r--r--lib/tools/test/xref_SUITE_data/dir/jam/x.jam0
-rw-r--r--lib/tools/test/xref_SUITE_data/lib_test/cp.erl6
-rw-r--r--lib/tools/test/xref_SUITE_data/lib_test/lib1.erl6
-rw-r--r--lib/tools/test/xref_SUITE_data/lib_test/lib2.erl14
-rw-r--r--lib/tools/test/xref_SUITE_data/lib_test/lib3.erl11
-rw-r--r--lib/tools/test/xref_SUITE_data/lib_test/t.erl14
-rw-r--r--lib/tools/test/xref_SUITE_data/md/x__x.erl7
-rw-r--r--lib/tools/test/xref_SUITE_data/md/y__y.erl12
-rw-r--r--lib/tools/test/xref_SUITE_data/read/read.beam.v1bin0 -> 7160 bytes
-rw-r--r--lib/tools/test/xref_SUITE_data/read/read.erl175
-rw-r--r--lib/tools/test/xref_SUITE_data/rel2/lib/app1-1.0/ebin/dummy2
-rw-r--r--lib/tools/test/xref_SUITE_data/rel2/lib/app1-1.1/ebin/dummy2
-rw-r--r--lib/tools/test/xref_SUITE_data/rel2/lib/app2-1.1/ebin/dummy2
-rw-r--r--lib/tools/test/xref_SUITE_data/rel2/x.erl16
-rw-r--r--lib/tools/test/xref_SUITE_data/rel2/y.erl6
-rw-r--r--lib/tools/test/xref_SUITE_data/update/x.erl.16
-rw-r--r--lib/tools/test/xref_SUITE_data/update/x.erl.26
-rw-r--r--lib/tools/vsn.mk2
68 files changed, 8863 insertions, 86 deletions
diff --git a/lib/tools/doc/src/erlang_mode.xml b/lib/tools/doc/src/erlang_mode.xml
index 72770898c2..912c442153 100644
--- a/lib/tools/doc/src/erlang_mode.xml
+++ b/lib/tools/doc/src/erlang_mode.xml
@@ -51,7 +51,7 @@
<list type="bulleted">
<item><em><c>TAB</c></em> (<c>erlang-indent-command</c>) -
Indents the current line of code. </item>
- <item><em><c>M-C-\\</c></em> (<c>indent-region</c>) - Indents all
+ <item><em><c>M-C-\</c></em> (<c>indent-region</c>) - Indents all
lines in the region. </item>
<item><em><c>M-l</c></em> (<c>indent-for-comment</c>) - Insert a
comment character to the right of the code on the line (if
diff --git a/lib/tools/doc/src/erlang_mode_chapter.xml b/lib/tools/doc/src/erlang_mode_chapter.xml
index cf043e3302..b22c6b1809 100644
--- a/lib/tools/doc/src/erlang_mode_chapter.xml
+++ b/lib/tools/doc/src/erlang_mode_chapter.xml
@@ -74,10 +74,10 @@
environment variable is set, Emacs will look for the
<c>.emacs</c> file in the directory indicated by the
<em>HOME</em> variable. If <em>HOME</em> is not set, Emacs
- will look for the <c>.emacs</c> file in <c>C:\\ </c>.</p>
+ will look for the <c>.emacs</c> file in <c>C:\ </c>.</p>
<p>Below is a complete example of what should be added to a user's
<c>.emacs</c> provided that OTP is installed in the directory
- <c><![CDATA[C:\\Program Files\\erl<Ver>]]></c>: </p>
+ <c><![CDATA[C:\Program Files\erl<Ver>]]></c>: </p>
<code type="none"><![CDATA[
(setq load-path (cons "C:/Program Files/erl<Ver>/lib/tools-<ToolsVer>/emacs"
load-path))
@@ -87,7 +87,7 @@
]]></code>
<note>
<p>In .emacs, the slash character "/" can be used as path
- separator. But if you decide to use the backslash character "\\",
+ separator. But if you decide to use the backslash character "\",
please not that you must use double backslashes, since they are
treated as escape characters by Emacs.</p>
</note>
diff --git a/lib/tools/doc/src/notes.xml b/lib/tools/doc/src/notes.xml
index 59f600145e..e6c074cc7d 100644
--- a/lib/tools/doc/src/notes.xml
+++ b/lib/tools/doc/src/notes.xml
@@ -30,6 +30,21 @@
</header>
<p>This document describes the changes made to the Tools application.</p>
+<section><title>Tools 2.6.5.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>A bug concerning bit comprehensions has been fixed
+ in Cover. The bug was introduced in R13B03.
+ (Thanks to Matthew Sackman.)</p>
+ <p>Own Id: OTP-8340</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Tools 2.6.5</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/tools/doc/src/notes_history.xml b/lib/tools/doc/src/notes_history.xml
index ef5ce1c03d..3791d5270a 100644
--- a/lib/tools/doc/src/notes_history.xml
+++ b/lib/tools/doc/src/notes_history.xml
@@ -4,23 +4,21 @@
<chapter>
<header>
<copyright>
- <year>2006</year>
- <year>2007</year>
- <holder>Ericsson AB, All Rights Reserved</holder>
+ <year>2006</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
- The contents of this file are subject to the Erlang Public License,
- Version 1.1, (the "License"); you may not use this file except in
- compliance with the License. You should have received a copy of the
- Erlang Public License along with this software. If not, it can be
- retrieved online at http://www.erlang.org/.
-
- Software distributed under the License is distributed on an "AS IS"
- basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
- the License for the specific language governing rights and limitations
- under the License.
-
- The Initial Developer of the Original Code is Ericsson AB.
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
</legalnotice>
<title>Tools Release Notes</title>
@@ -63,7 +61,7 @@
<p>Own Id: OTP-5073</p>
</item>
<item>
- <p>Previous patch from open source messed up \\M-q so part of
+ <p>Previous patch from open source messed up \M-q so part of
that patch was backed out.</p>
<p>Own Id: OTP-5074</p>
</item>
diff --git a/lib/tools/doc/src/xref.xml b/lib/tools/doc/src/xref.xml
index 6fff68fe9f..407a7392ad 100644
--- a/lib/tools/doc/src/xref.xml
+++ b/lib/tools/doc/src/xref.xml
@@ -1176,7 +1176,7 @@ Evaluates a predefined analysis.
<item>
<p><c>no_functions</c>. Functions in library modules and
the functions <c>module_info/0,1</c> are not counted by
- <c>info</c>. Assuming that <c>"Extra := _:module_info/\\"(0|1)\\" + LM"</c> has been evaluated, the
+ <c>info</c>. Assuming that <c>"Extra := _:module_info/\"(0|1)\" + LM"</c> has been evaluated, the
sum of the number of local and exported functions are:</p>
<list type="bulleted">
<item><c>"# (F - Extra)"</c> (info/1)</item>
diff --git a/lib/tools/emacs/erlang.el b/lib/tools/emacs/erlang.el
index f623e3a1ee..4fc4826238 100644
--- a/lib/tools/emacs/erlang.el
+++ b/lib/tools/emacs/erlang.el
@@ -72,7 +72,7 @@
;; Variables:
-(defconst erlang-version "2.6.1"
+(defconst erlang-version "2.6.2"
"The version number of Erlang mode.")
(defvar erlang-root-dir nil
@@ -3792,9 +3792,9 @@ Value is list (stack token-start token-type in-what)."
;; Clause end
((= (following-char) ?\;)
- (if (and stack (and (eq (car (car stack)) 'when)
- (eq (car (car (cdr (cdr stack)))) 'spec)))
- (erlang-pop stack))
+ (if (eq (car (car (last stack))) 'spec)
+ (while (memq (car (car stack)) '(when ::))
+ (erlang-pop stack)))
(if (and stack (eq (car (car stack)) '->))
(erlang-pop stack))
(forward-char 1))
@@ -3955,15 +3955,16 @@ Return nil if inside string, t if in a comment."
(nth 2 stack-top))))
(t
(goto-char (nth 1 stack-top))
- (cond ((looking-at "[({]\\s *\\($\\|%\\)")
- ;; Line ends with parenthesis.
- (erlang-indent-parenthesis (nth 2 stack-top)))
- (t
- ;; Indent to the same column as the first
- ;; argument.
- (goto-char (1+ (nth 1 stack-top)))
- (skip-chars-forward " \t")
- (current-column))))))
+ (let ((base (cond ((looking-at "[({]\\s *\\($\\|%\\)")
+ ;; Line ends with parenthesis.
+ (erlang-indent-parenthesis (nth 2 stack-top)))
+ (t
+ ;; Indent to the same column as the first
+ ;; argument.
+ (goto-char (1+ (nth 1 stack-top)))
+ (skip-chars-forward " \t")
+ (current-column)))))
+ (erlang-indent-standard indent-point token base 't)))))
;;
((eq (car stack-top) '<<)
;; Element of binary (possible comprehension) expression,
@@ -4047,33 +4048,8 @@ Return nil if inside string, t if in a comment."
0))
base)) ;; old catch
(t
- ;; Look at last thing to see how we are to move relative
- ;; to the base.
- (goto-char token)
- (cond ((looking-at "||\\|,\\|->")
- base)
- ((erlang-at-keyword)
- (+ (current-column) erlang-indent-level))
- ((or (= (char-syntax (following-char)) ?.)
- (erlang-at-operator))
- (+ base erlang-indent-level))
- (t
- (goto-char indent-point)
- (cond ((memq (following-char) '(?\( ?{))
- ;; Function application or record.
- (+ (erlang-indent-find-preceding-expr)
- erlang-argument-indent))
- ;; Empty line, or end; treat it as the end of
- ;; the block. (Here we have a choice: should
- ;; the user be forced to reindent continued
- ;; lines, or should the "end" be reindented?)
-
- ;; Avoid treating comments a continued line.
- ((= (following-char) ?%)
- base)
- ;; Continued line (e.g. line beginning
- ;; with an operator.)
- (t (+ base erlang-indent-level)))))))))
+ (erlang-indent-standard indent-point token base 'nil)
+ ))))
))
((eq (car stack-top) 'when)
(goto-char (nth 1 stack-top))
@@ -4105,21 +4081,55 @@ Return nil if inside string, t if in a comment."
(+ 2 (nth 2 stack-top)))
((looking-at "::[^_a-zA-Z0-9]")
(nth 2 stack-top))
- (t
- (goto-char (nth 1 stack-top))
- (cond ((looking-at "::\\s *\\($\\|%\\)")
- ;; Line ends with ::
- (+ (erlang-indent-find-preceding-expr 2)
- erlang-argument-indent))
- ;; (* 2 erlang-indent-level))
- (t
- ;; Indent to the same column as the first
- ;; argument.
- (goto-char (+ 2 (nth 1 stack-top)))
- (skip-chars-forward " \t")
- (current-column))))))
+ (t
+ (let ((start-alternativ (if (looking-at "|") 2 0)))
+ (goto-char (nth 1 stack-top))
+ (- (cond ((looking-at "::\\s *\\($\\|%\\)")
+ ;; Line ends with ::
+ (if (eq (car (car (last stack))) 'spec)
+ (+ (erlang-indent-find-preceding-expr 1)
+ erlang-argument-indent)
+ (+ (erlang-indent-find-preceding-expr 2)
+ erlang-argument-indent)))
+ (t
+ ;; Indent to the same column as the first
+ ;; argument.
+ (goto-char (+ 2 (nth 1 stack-top)))
+ (skip-chars-forward " \t")
+ (current-column))) start-alternativ)))))
)))
+(defun erlang-indent-standard (indent-point token base inside-parenthesis)
+ "Standard indent when in blocks or tuple or arguments.
+ Look at last thing to see in what state we are, move relative to the base."
+ (goto-char token)
+ (cond ((looking-at "||\\|,\\|->\\||")
+ base)
+ ((erlang-at-keyword)
+ (+ (current-column) erlang-indent-level))
+ ((or (= (char-syntax (following-char)) ?.)
+ (erlang-at-operator))
+ (+ base erlang-indent-level))
+ (t
+ (goto-char indent-point)
+ (cond ((memq (following-char) '(?\( ?{))
+ ;; Function application or record.
+ (+ (erlang-indent-find-preceding-expr)
+ erlang-argument-indent))
+ ;; Empty line, or end; treat it as the end of
+ ;; the block. (Here we have a choice: should
+ ;; the user be forced to reindent continued
+ ;; lines, or should the "end" be reindented?)
+
+ ;; Avoid treating comments a continued line.
+ ((= (following-char) ?%)
+ base)
+ ;; Continued line (e.g. line beginning
+ ;; with an operator.)
+ (t
+ (if (or (erlang-at-operator) (not inside-parenthesis))
+ (+ base erlang-indent-level)
+ base))))))
(defun erlang-indent-find-base (stack indent-point &optional offset skip)
"Find the base column for current stack."
@@ -4946,6 +4956,7 @@ non-whitespace characters following the point on the current line."
(setq erlang-electric-newline-inhibit nil)
(setq erlang-electric-newline-inhibit t)
(undo-boundary)
+ (erlang-indent-line)
(end-of-line)
(newline)
(condition-case nil
diff --git a/lib/tools/emacs/test.erl.indented b/lib/tools/emacs/test.erl.indented
index b2cc23b92b..1ccced9177 100644
--- a/lib/tools/emacs/test.erl.indented
+++ b/lib/tools/emacs/test.erl.indented
@@ -44,6 +44,24 @@
b
}).
+-record(record3, {a = 8#42423 bor
+ 8#4234,
+ b = 8#5432
+ bor 2#1010101
+ c = 123 +
+ 234,
+ d}).
+
+-record(record4, {
+ a = 8#42423 bor
+ 8#4234,
+ b = 8#5432
+ bor 2#1010101
+ c = 123 +
+ 234,
+ d}).
+
+
-define(MACRO_1, macro).
-define(MACRO_2(_), macro).
@@ -51,8 +69,10 @@
-type ann() :: Var :: integer().
-type ann2() :: Var ::
- 'return' | 'return_white_spaces' | 'return_comments'
- | 'text' | ann().
+ 'return'
+ | 'return_white_spaces'
+ | 'return_comments'
+ | 'text' | ann().
-type paren() ::
(ann2()).
-type t1() :: atom().
@@ -89,7 +109,7 @@
fun((nonempty_maybe_improper_list('integer', any())|
1|2|3|a|b|<<_:3,_:_*14>>|integer()) ->
nonempty_maybe_improper_list('integer', any())|
- 1|2|3|a|b|<<_:3,_:_*14>>|integer()).
+ 1|2|3|a|b|<<_:3,_:_*14>>|integer()).
-type t20() :: [t19(), ...].
-type t21() :: tuple().
-type t21(A) :: A.
@@ -110,7 +130,28 @@
(t24()) -> t24() when is_subtype(t24(), atom()),
is_subtype(t24(), t14()),
is_subtype(t24(), t4()).
+
+-spec over(I :: integer()) -> R1 :: foo:typen();
+ (A :: atom()) -> R2 :: foo:atomen();
+ (T :: tuple()) -> R3 :: bar:typen().
+
-spec mod:t2() -> any().
+
+-spec handle_cast(Cast :: {'exchange', node(), [[name(),...]]}
+ | {'del_member', name(), pid()},
+ #state{}) -> {'noreply', #state{}}.
+
+-spec handle_cast(Cast ::
+ {'exchange', node(), [[name(),...]]}
+ | {'del_member', name(), pid()},
+ #state{}) -> {'noreply', #state{}}.
+
+
+-spec get_closest_pid(term()) ->
+ Return :: pid()
+ | {'error', {'no_process', term()}
+ | {'no_such_group', term()}}.
+
-opaque attributes_data() ::
[{'column', column()} | {'line', info_line()} |
{'text', string()}] | {line(),column()}.
@@ -277,7 +318,10 @@ indent_basics(X, Y, Z)
c
),
-
+ call(2#42423 bor
+ #4234,
+ 2#5432,
+ other_arg),
ok;
indent_basics(Xlongname,
#struct{a=Foo,
@@ -359,7 +403,7 @@ indent_icr(Z) -> % icr = if case receive
X = 43 div 4,
foo(X)
end,
- receive
+ receive
{Z,_,_} ->
X = 43 div 4,
foo(X);
@@ -491,7 +535,7 @@ indent_catch() ->
B = catch oskar(X),
A = catch (baz +
- bax),
+ bax),
catch foo(),
C = catch B +
diff --git a/lib/tools/emacs/test.erl.orig b/lib/tools/emacs/test.erl.orig
index 773998a4c6..9b4203120b 100644
--- a/lib/tools/emacs/test.erl.orig
+++ b/lib/tools/emacs/test.erl.orig
@@ -44,6 +44,24 @@
b
}).
+-record(record3, {a = 8#42423 bor
+ 8#4234,
+ b = 8#5432
+ bor 2#1010101
+ c = 123 +
+234,
+ d}).
+
+-record(record4, {
+ a = 8#42423 bor
+ 8#4234,
+ b = 8#5432
+ bor 2#1010101
+ c = 123 +
+ 234,
+ d}).
+
+
-define(MACRO_1, macro).
-define(MACRO_2(_), macro).
@@ -51,8 +69,10 @@
-type ann() :: Var :: integer().
-type ann2() :: Var ::
- 'return' | 'return_white_spaces' | 'return_comments'
- | 'text' | ann().
+ 'return'
+ | 'return_white_spaces'
+ | 'return_comments'
+ | 'text' | ann().
-type paren() ::
(ann2()).
-type t1() :: atom().
@@ -110,7 +130,28 @@ t15(),t20(),t21(), t22(),t25()}.
(t24()) -> t24() when is_subtype(t24(), atom()),
is_subtype(t24(), t14()),
is_subtype(t24(), t4()).
+
+-spec over(I :: integer()) -> R1 :: foo:typen();
+ (A :: atom()) -> R2 :: foo:atomen();
+ (T :: tuple()) -> R3 :: bar:typen().
+
-spec mod:t2() -> any().
+
+-spec handle_cast(Cast :: {'exchange', node(), [[name(),...]]}
+ | {'del_member', name(), pid()},
+ #state{}) -> {'noreply', #state{}}.
+
+-spec handle_cast(Cast ::
+ {'exchange', node(), [[name(),...]]}
+ | {'del_member', name(), pid()},
+ #state{}) -> {'noreply', #state{}}.
+
+
+-spec get_closest_pid(term()) ->
+ Return :: pid()
+ | {'error', {'no_process', term()}
+ | {'no_such_group', term()}}.
+
-opaque attributes_data() ::
[{'column', column()} | {'line', info_line()} |
{'text', string()}] | {line(),column()}.
@@ -277,7 +318,10 @@ Y =:= 4711 ->
c
),
-
+ call(2#42423 bor
+ #4234,
+ 2#5432,
+ other_arg),
ok;
indent_basics(Xlongname,
#struct{a=Foo,
diff --git a/lib/tools/src/cover.erl b/lib/tools/src/cover.erl
index aff3927db3..1a7ebdc69a 100644
--- a/lib/tools/src/cover.erl
+++ b/lib/tools/src/cover.erl
@@ -1685,8 +1685,8 @@ munge_expr({lc,Line,Expr,Qs}, Vars) ->
{MungedQs, Vars3} = munge_qualifiers(Qs, Vars2),
{{lc,Line,MungedExpr,MungedQs}, Vars3};
munge_expr({bc,Line,Expr,Qs}, Vars) ->
- {bin,BLine,[{bin_element,EL,Val,Sz,TSL}]} = Expr,
- Expr2 = {bin,BLine,[{bin_element,EL,?BLOCK1(Val),Sz,TSL}]},
+ {bin,BLine,[{bin_element,EL,Val,Sz,TSL}|Es]} = Expr,
+ Expr2 = {bin,BLine,[{bin_element,EL,?BLOCK1(Val),Sz,TSL}|Es]},
{MungedExpr,Vars2} = munge_expr(Expr2, Vars),
{MungedQs, Vars3} = munge_qualifiers(Qs, Vars2),
{{bc,Line,MungedExpr,MungedQs}, Vars3};
diff --git a/lib/tools/test/Makefile b/lib/tools/test/Makefile
new file mode 100644
index 0000000000..a846a3a6f4
--- /dev/null
+++ b/lib/tools/test/Makefile
@@ -0,0 +1,90 @@
+#
+# %CopyrightBegin%
+#
+# Copyright Ericsson AB 1997-2010. All Rights Reserved.
+#
+# The contents of this file are subject to the Erlang Public License,
+# Version 1.1, (the "License"); you may not use this file except in
+# compliance with the License. You should have received a copy of the
+# Erlang Public License along with this software. If not, it can be
+# retrieved online at http://www.erlang.org/.
+#
+# Software distributed under the License is distributed on an "AS IS"
+# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+# the License for the specific language governing rights and limitations
+# under the License.
+#
+# %CopyrightEnd%
+#
+include $(ERL_TOP)/make/target.mk
+include $(ERL_TOP)/make/$(TARGET)/otp.mk
+
+MODULES = \
+ cover_SUITE \
+ eprof_SUITE \
+ emem_SUITE \
+ fprof_SUITE \
+ cprof_SUITE \
+ instrument_SUITE \
+ make_SUITE \
+ tools_SUITE \
+ xref_SUITE \
+ ignore_cores
+
+ERL_FILES= $(MODULES:%=%.erl)
+
+TARGET_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR))
+INSTALL_PROGS= $(TARGET_FILES)
+
+EMAKEFILE=Emakefile
+
+SPEC_FILES= tools.spec tools.spec.win
+
+# ----------------------------------------------------
+# Release directory specification
+# ----------------------------------------------------
+RELSYSDIR = $(RELEASE_PATH)/tools_test
+
+# ----------------------------------------------------
+# FLAGS
+# ----------------------------------------------------
+ERL_MAKE_FLAGS +=
+ERL_COMPILE_FLAGS += -I$(ERL_TOP)/lib/test_server/include
+
+EBIN = .
+
+# ----------------------------------------------------
+# Targets
+# ----------------------------------------------------
+.PHONY: make_emakefile
+
+make_emakefile:
+ $(ERL_TOP)/make/make_emakefile $(ERL_COMPILE_FLAGS) -o$(EBIN) $(MODULES)\
+ > $(EMAKEFILE)
+
+tests debug opt: make_emakefile
+ erl $(ERL_MAKE_FLAGS) -make
+
+clean:
+ rm -f $(EMAKEFILE)
+ rm -f $(TARGET_FILES)
+ rm -f core
+
+docs:
+
+# ----------------------------------------------------
+# Release Target
+# ----------------------------------------------------
+include $(ERL_TOP)/make/otp_release_targets.mk
+
+release_spec: opt
+
+release_tests_spec: make_emakefile
+ $(INSTALL_DIR) $(RELSYSDIR)
+ $(INSTALL_DATA) $(SPEC_FILES) $(EMAKEFILE) $(ERL_FILES) $(RELSYSDIR)
+ chmod -f -R u+w $(RELSYSDIR)
+ @tar cf - *_SUITE_data | (cd $(RELSYSDIR); tar xf -)
+
+release_docs_spec:
+
+
diff --git a/lib/tools/test/cover_SUITE.erl b/lib/tools/test/cover_SUITE.erl
new file mode 100644
index 0000000000..b9ccd62d0b
--- /dev/null
+++ b/lib/tools/test/cover_SUITE.erl
@@ -0,0 +1,1198 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2001-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(cover_SUITE).
+
+-export([all/1]).
+-export([start/1, compile/1, analyse/1, misc/1, stop/1,
+ distribution/1, export_import/1,
+ otp_5031/1, eif/1, otp_5305/1, otp_5418/1, otp_6115/1, otp_7095/1,
+ otp_8188/1, otp_8270/1, otp_8273/1, otp_8340/1]).
+
+-include("test_server.hrl").
+
+%%----------------------------------------------------------------------
+%% The following directory structure is assumed:
+%% cwd __________________________________________
+%% | \ \ \ \ \ \ \
+%% a b cc d f d1 compile_beam_____ otp_6115
+%% | \ \ \ \ \ \ \
+%% e crypt v w x d f1 f2
+%% |
+%% y
+%%----------------------------------------------------------------------
+
+all(suite) ->
+ case whereis(cover_server) of
+ undefined ->
+ [start, compile, analyse, misc, stop, distribution,
+ export_import,
+ otp_5031, eif, otp_5305, otp_5418, otp_6115, otp_7095,
+ otp_8188, otp_8270, otp_8273, otp_8340];
+ _pid ->
+ {skip,"It looks like the test server is running cover. "
+ "Can't run cover test."}
+ end.
+
+start(suite) -> [];
+start(Config) when is_list(Config) ->
+ ?line ok = file:set_cwd(?config(data_dir, Config)),
+
+ ?line Files = lsfiles(),
+ ?line remove(files(Files, ".out")),
+
+ ?line {ok, Pid} = cover:start(),
+ ?line {error, {already_started, Pid}} = cover:start().
+
+compile(suite) -> [];
+compile(Config) when is_list(Config) ->
+ ?line ok = file:set_cwd(?config(data_dir, Config)),
+
+ ?line Result1 = cover:compile_directory(),
+ ?line SortedResult = lists:sort(Result1),
+ ?line {ok, CWD} = file:get_cwd(),
+ ?line Result2 = cover:compile_directory(CWD),
+ ?line SortedResult = lists:sort(Result2),
+ ?line [{error,_DFile},{ok,a},{ok,b},{ok,cc},{ok,f}] = SortedResult,
+ ?line [{ok,e}] = cover:compile_directory("d1"),
+ ?line {error,enoent} = cover:compile_directory("d2"),
+
+ ?line {ok,a} = cover:compile(a),
+ ?line {ok,b} = compile:file(b),
+ ?line code:purge(b),
+ ?line {module,b} = code:load_file(b),
+ ?line {ok,d} = cover:compile("d.erl", [{d,'AGE',42}]),
+ ?line {error,_BBFile} = cover:compile(bb),
+
+ ?line StdlibDir = code:lib_dir(stdlib),
+ ?line Lists = filename:join([StdlibDir, "src", "lists.erl"]),
+ ?line {error, Lists} = cover:compile(Lists),
+
+ %% For compiling beam: using dummy files v,w,x,y and z
+ ?line file:set_cwd("compile_beam"),
+ ?line {ok,_} = compile:file(v,[debug_info,report]),
+ ?line {ok,_} = compile:file(w,[debug_info,report]),
+ ?line {ok,_} = compile:file(x),
+ ?line {ok,_} = compile:file("d/y",[debug_info,{outdir,"d"},report]),
+ ?line Key = "A Krypto Key",
+ ?line {ok,_} = compile:file(crypt, [debug_info,{debug_info_key,Key},report]),
+ ?line {ok,v} = cover:compile_beam(v),
+ ?line {ok,w} = cover:compile_beam("w.beam"),
+ ?line {error,{encrypted_abstract_code,_}} =
+ cover:compile_beam("crypt.beam"),
+ ?line ok = beam_lib:crypto_key_fun(simple_crypto_fun(Key)),
+ ?line {ok,crypt} = cover:compile_beam("crypt.beam"),
+ ?line {error,{no_abstract_code,"./x.beam"}} = cover:compile_beam(x),
+ ?line {error,{already_cover_compiled,no_beam_found,a}}=cover:compile_beam(a),
+ ?line {error,non_existing} = cover:compile_beam(z),
+ ?line [{ok,y}] = cover:compile_beam_directory("d"),
+ ?line Result3 = lists:sort(cover:compile_beam_directory()),
+ ?line [{error,{no_abstract_code,_XBeam}},{ok,crypt},{ok,v},{ok,w}] = Result3,
+ ?line {error,enoent} = cover:compile_beam_directory("d2"),
+ ?line decompile([v,w,y]),
+ ?line Files = lsfiles(),
+ ?line remove(files(Files, ".beam")).
+
+simple_crypto_fun(Key) ->
+ fun(init) -> ok;
+ ({debug_info, des3_cbc, crypt, _}) -> Key
+ end.
+
+analyse(suite) -> [];
+analyse(Config) when is_list(Config) ->
+ ?line ok = file:set_cwd(?config(data_dir, Config)),
+
+ ?line done = a:start(5),
+
+ ?line {ok, {a,{17,2}}} = cover:analyse(a, coverage, module),
+ ?line {ok, [{{a,start,1},{6,0}},
+ {{a,stop,1},{0,1}},
+ {{a,pong,1},{1,0}},
+ {{a,loop,3},{5,1}},
+ {{a,trycatch,1},{4,0}},
+ {{a,exit_kalle,0},{1,0}}]} = cover:analyse(a, coverage, function),
+ ?line {ok, [{{a,start,1,1},{6,0}},
+ {{a,stop,1,1},{0,1}},
+ {{a,pong,1,1},{1,0}},
+ {{a,loop,3,1},{3,1}},
+ {{a,loop,3,2},{2,0}},
+ {{a,trycatch,1,1},{4,0}},
+ {{a,exit_kalle,0,1},{1,0}}]} = cover:analyse(a, coverage, clause),
+ ?line {ok, [{{a,9},{1,0}},
+ {{a,10},{1,0}},
+ {{a,11},{1,0}},
+ {{a,13},{1,0}},
+ {{a,14},{1,0}},
+ {{a,15},{1,0}},
+ {{a,21},{0,1}},
+ {{a,26},{1,0}},
+ {{a,31},{1,0}},
+ {{a,32},{1,0}},
+ {{a,34},{1,0}},
+ {{a,36},{0,1}},
+ {{a,39},{1,0}},
+ {{a,40},{1,0}},
+ {{a,44},{1,0}},
+ {{a,47},{1,0}},
+ {{a,49},{1,0}},
+ {{a,51},{1,0}},
+ {{a,55},{1,0}}]} = cover:analyse(a, coverage, line),
+
+ ?line {ok, {a,15}} = cover:analyse(a, calls, module),
+ ?line {ok, [{{a,start,1},1},
+ {{a,stop,1},0},
+ {{a,pong,1},5},
+ {{a,loop,3},6},
+ {{a,trycatch,1},2},
+ {{a,exit_kalle,0},1}]} = cover:analyse(a, calls, function),
+ ?line {ok, [{{a,start,1,1},1},
+ {{a,stop,1,1},0},
+ {{a,pong,1,1},5},
+ {{a,loop,3,1},5},
+ {{a,loop,3,2},1},
+ {{a,trycatch,1,1},2},
+ {{a,exit_kalle,0,1},1}]} = cover:analyse(a, calls, clause),
+ ?line {ok, [{{a,9},1},
+ {{a,10},1},
+ {{a,11},1},
+ {{a,13},1},
+ {{a,14},1},
+ {{a,15},1},
+ {{a,21},0},
+ {{a,26},5},
+ {{a,31},5},
+ {{a,32},5},
+ {{a,34},5},
+ {{a,36},0},
+ {{a,39},1},
+ {{a,40},1},
+ {{a,44},2},
+ {{a,47},1},
+ {{a,49},1},
+ {{a,51},2},
+ {{a,55},1}]} = cover:analyse(a, calls, line),
+
+ ?line {ok, [{{a,start,1},{6,0}},
+ {{a,stop,1},{0,1}},
+ {{a,pong,1},{1,0}},
+ {{a,loop,3},{5,1}},
+ {{a,trycatch,1},{4,0}},
+ {{a,exit_kalle,0},{1,0}}]} = cover:analyse(a),
+ ?line {ok, {a,{17,2}}} = cover:analyse(a, module),
+ ?line {ok, [{{a,start,1},1},
+ {{a,stop,1},0},
+ {{a,pong,1},5},
+ {{a,loop,3},6},
+ {{a,trycatch,1},2},
+ {{a,exit_kalle,0},1}]} = cover:analyse(a, calls),
+
+ ?line {ok, "a.COVER.out"} = cover:analyse_to_file(a),
+ ?line {ok, "e.COVER.out"} = cover:analyse_to_file(e),
+ ?line {ok, "a.COVER.html"} = cover:analyse_to_file(a,[html]),
+ ?line {ok, "e.COVER.html"} = cover:analyse_to_file(e,[html]),
+
+ %% analyse_to_file of file which is compiled from beam
+ ?line {ok,f} = compile:file(f,[debug_info]),
+ ?line code:purge(f),
+ ?line {module,f} = code:load_file(f),
+ ?line {ok,f} = cover:compile_beam(f),
+ ?line f:f2(),
+ ?line {ok, "f.COVER.out"} = cover:analyse_to_file(f),
+
+ %% Source code cannot be found by analyse_to_file
+ ?line {ok,v} = compile:file("compile_beam/v",[debug_info]),
+ ?line code:purge(v),
+ ?line {module,v} = code:load_file(v),
+ ?line {ok,v} = cover:compile_beam(v),
+ ?line {error,no_source_code_found} = cover:analyse_to_file(v),
+
+ ?line {error,{not_cover_compiled,b}} = cover:analyse(b),
+ ?line {error,{not_cover_compiled,g}} = cover:analyse(g),
+ ?line {error,{not_cover_compiled,b}} = cover:analyse_to_file(b),
+ ?line {error,{not_cover_compiled,g}} = cover:analyse_to_file(g).
+
+misc(suite) -> [];
+misc(Config) when is_list(Config) ->
+ ?line ok = file:set_cwd(?config(data_dir, Config)),
+
+ ?line [a,cc,crypt,d,e,f,v] = lists:sort(cover:modules()),
+
+ ?line {ok,cc} = compile:file(cc),
+ ?line code:purge(cc),
+ ?line {module,cc} = code:load_file(cc),
+ ?line [a,crypt,d,e,f,v] = lists:sort(cover:modules()),
+
+ ?line {file, _File} = cover:is_compiled(a),
+ ?line false = cover:is_compiled(b),
+ ?line false = cover:is_compiled(g),
+
+ ?line ok = cover:reset(a),
+ ?line {ok, {a,{0,19}}} = cover:analyse(a, module),
+ ?line ok = cover:reset().
+
+stop(suite) -> [];
+stop(Config) when is_list(Config) ->
+ ?line ok = file:set_cwd(?config(data_dir, Config)),
+
+ ?line cover_compiled = code:which(a),
+ ?line {ok,d} = compile:file(d, [{d,'AGE',42}]),
+ ?line code:purge(d),
+ ?line {module,d} = code:load_file(d),
+ ?line ok = cover:stop(),
+ ?line Beam = code:which(a),
+ ?line true = is_unloaded(Beam),
+
+ ?line Files = lsfiles(),
+ ?line remove(files(Files, ".out")),
+ ?line remove(files(Files, ".html")),
+ ?line remove(files(Files, ".beam")).
+
+distribution(suite) -> [];
+distribution(Config) when is_list(Config) ->
+ ?line DataDir = ?config(data_dir, Config),
+ ?line ok = file:set_cwd(DataDir),
+
+ ?line {ok,N1} = ?t:start_node(cover_SUITE_distribution1,slave,[]),
+ ?line {ok,N2} = ?t:start_node(cover_SUITE_distribution2,slave,[]),
+ ?line {ok,N3} = ?t:start_node(cover_SUITE_distribution3,slave,[]),
+
+ %% Check that an already compiled module is loaded on new nodes
+ ?line {ok,f} = cover:compile(f),
+ ?line {ok,[_,_,_]} = cover:start(nodes()),
+ ?line cover_compiled = code:which(f),
+ ?line cover_compiled = rpc:call(N1,code,which,[f]),
+ ?line cover_compiled = rpc:call(N2,code,which,[f]),
+ ?line cover_compiled = rpc:call(N3,code,which,[f]),
+
+ %% Check that a node cannot be started twice
+ ?line {ok,[]} = cover:start(N2),
+
+ %% Check that the current node (i.e. the main node) is not started with
+ %% start/1 and not stopped with stop/1
+ ?line {ok,[]} = cover:start(node()),
+ ?line ok = cover:stop(node()),
+ ?line true = is_pid(whereis(cover_server)),
+
+ %% Check that a new compiled module is loaded on all existing nodes
+ ?line compile:file("compile_beam/v",[debug_info]),
+ ?line {ok,v} = cover:compile_beam(v),
+ ?line cover_compiled = code:which(v),
+ ?line cover_compiled = rpc:call(N1,code,which,[v]),
+ ?line cover_compiled = rpc:call(N2,code,which,[v]),
+ ?line cover_compiled = rpc:call(N3,code,which,[v]),
+
+ %% this is lost when the node is killed
+ ?line rpc:call(N3,f,f2,[]),
+ ?line rpc:call(N3,erlang,halt,[]),
+
+ %% this should be visible in analyse
+ ?line rpc:call(N1,f,f1,[]),
+
+ %% Check that data is collected from remote node when stopped
+ ?line ok = cover:stop(N1),
+ ?line N1Beam = rpc:call(N1,code,which,[f]),
+ ?line true = is_unloaded(N1Beam),
+ ?line check_f_calls(1,0),
+
+ %% Call f:f1() again on another node and check that number of calls is
+ %% accumulated.
+ ?line f:f1(),
+ ?line check_f_calls(2,0),
+
+ %% Check that reset works on all nodes
+ ?line f:f1(),
+ ?line rpc:call(N2,f,f1,[]),
+ ?line ok = cover:reset(f),
+ ?line check_f_calls(0,0),
+
+ %% Check that data is collected from all nodes
+ ?line rpc:call(N2,f,f1,[]),
+ ?line f:f2(),
+ ?line check_f_calls(1,1),
+
+ %% Check that same data is not fetched again (i.e. that analyse does
+ %% reset on the remote node(s))
+ ?line check_f_calls(1,1),
+
+ %% Check that stop() unloads on all nodes
+ ?line ok = cover:stop(),
+ ?line LocalBeam = code:which(f),
+ ?line N2Beam = rpc:call(N2,code,which,[f]),
+ ?line true = is_unloaded(LocalBeam),
+ ?line true = is_unloaded(N2Beam),
+
+ %% Check that cover_server on remote node dies if main node dies
+ ?line {ok,[N1]} = cover:start(N1),
+ ?line true = is_pid(rpc:call(N1,erlang,whereis,[cover_server])),
+ ?line exit(whereis(cover_server),kill),
+ ?line timer:sleep(10),
+ ?line undefined = rpc:call(N1,erlang,whereis,[cover_server]),
+
+ %% Cleanup
+ ?line Files = lsfiles(),
+ ?line remove(files(Files, ".beam")),
+ ?line ?t:stop_node(N1),
+ ?line ?t:stop_node(N2).
+
+
+export_import(suite) -> [];
+export_import(Config) when is_list(Config) ->
+ ?line DataDir = ?config(data_dir, Config),
+ ?line ok = file:set_cwd(DataDir),
+
+ %% Export one module
+ ?line {ok,f} = cover:compile(f),
+ ?line f:f1(),
+ %% check that no info is written about where data comes from when no
+ %% files are imported
+ ?line ?t:capture_start(),
+ ?line check_f_calls(1,0),
+ ?line [] = ?t:capture_get(),
+ ?line ?t:capture_stop(),
+ ?line ok = cover:export("f_exported",f),
+ ?line check_f_calls(1,0),
+ ?line ok = cover:stop(),
+
+ %% Check that same data exists after import and that info is written about
+ %% data comming from imported file
+ ?line ok = cover:import("f_exported"),
+ ?line ?t:capture_start(),
+ ?line check_f_calls(1,0),
+ ?line [Text1] = ?t:capture_get(),
+ ?line "Analysis includes data from imported files"++_ = lists:flatten(Text1),
+ ?line ?t:capture_stop(),
+
+ %% Export all modules
+ ?line {ok,a} = cover:compile(a),
+ ?line ?t:capture_start(),
+ ?line ok = cover:export("all_exported"),
+ ?line [Text2] = ?t:capture_get(),
+ ?line "Export includes data from imported files"++_ = lists:flatten(Text2),
+ ?line ?t:capture_stop(),
+ ?line ok = cover:stop(),
+ ?line ok = cover:import("all_exported"),
+ ?line check_f_calls(1,0),
+
+ %% Check that data is reset when module is compiled again, and that
+ %% warning is written when data is deleted for imported module.
+ ?line ?t:capture_start(),
+ ?line {ok,f} = cover:compile(f),
+ ?line timer:sleep(10), % capture needs some time
+ ?line [Text3] = ?t:capture_get(),
+ ?line "WARNING: Deleting data for module f imported from" ++ _ =
+ lists:flatten(Text3),
+ ?line ?t:capture_stop(),
+ ?line check_f_calls(0,0),
+
+ %% Check that data is summed up when first compiled and then imported
+ %% The module which has been compiled (f) is loaded from the file
+ %% all_exported again (since it has been reset during cover compiling),
+ %% but the other module (a) is not loaded since it is already loaded
+ ?line f:f1(),
+ ?line f:f2(),
+ ?line ok = cover:import("f_exported"),
+ ?line ?t:capture_start(),
+ ?line ok = cover:import("all_exported"),
+ ?line [Text4] = ?t:capture_get(), % a is not loaded again
+ ?line "WARNING: Module a already imported from " ++ _ = lists:flatten(Text4),
+ ?line ?t:capture_stop(),
+ ?line check_f_calls(3,1),
+
+ %% Check that warning is written when same file is imported twice,
+ %% and that data is not imported again
+ ?line ?t:capture_start(),
+ ?line ok = cover:import("all_exported"),
+ ?line [Text5,Text6] = ?t:capture_get(),
+ ?line "WARNING: Module f already imported from " ++ _ = lists:flatten(Text5),
+ ?line "WARNING: Module a already imported from " ++ _ = lists:flatten(Text6),
+ ?line ?t:capture_stop(),
+ ?line check_f_calls(3,1),
+
+ %% Check that reset removes all data and that the file which has been
+ %% reset can be imported again with no warning
+ ?line cover:reset(f),
+ ?line check_f_calls(0,0),
+ ?line ?t:capture_start(),
+ ?line ok = cover:import("all_exported"),
+ ?line [Text7] = ?t:capture_get(), % warning only on mod a
+ ?line "WARNING: Module a already imported from " ++ _ = lists:flatten(Text7),
+ ?line ?t:capture_stop(),
+ ?line check_f_calls(1,0),
+
+ %% same as above - only reset all
+ ?line cover:reset(),
+ ?line check_f_calls(0,0),
+ ?line ?t:capture_start(),
+ ?line ok = cover:import("all_exported"),
+ ?line [] = ?t:capture_get(), % no warnings
+ ?line ?t:capture_stop(),
+ ?line check_f_calls(1,0),
+
+ %% Cleanup
+ ?line ok = cover:stop(),
+ ?line Files = lsfiles(),
+ ?line remove(["f_exported","all_exported"|files(Files, ".beam")]).
+
+
+otp_5031(suite) -> [];
+otp_5031(Config) when is_list(Config) ->
+
+ Dog = ?t:timetrap(?t:seconds(10)),
+
+ ?line {ok,N1} = ?t:start_node(cover_SUITE_distribution1,slave,[]),
+ ?line {ok,[N1]} = cover:start(N1),
+ ?line {error,not_main_node} = rpc:call(N1,cover,modules,[]),
+ ?line cover:stop(),
+
+ ?t:timetrap_cancel(Dog),
+ ok.
+
+eif(doc) ->
+ ["Test the \'Exclude Included Functions\' functionality"];
+eif(suite) ->
+ [];
+eif(Config) when is_list(Config) ->
+ ?line ok = file:set_cwd(filename:join(?config(data_dir, Config),
+ "included_functions")),
+ ?line {ok, cover_inc} = compile:file(cover_inc,[debug_info]),
+ ?line {ok, cover_inc} = cover:compile_beam(cover_inc),
+
+ %% This function will cause an included function to be executed.
+ %% The analysis should only show the lines that actually exist
+ %% in cover_inc.beam - not the ones from the included file.
+ ?line cover_inc:func(),
+ ?line {ok, [_, _]} = cover:analyse(cover_inc, line),
+ ?line cover:stop(),
+ ok.
+
+otp_5305(suite) -> [];
+otp_5305(Config) when is_list(Config) ->
+ ?line ok = file:set_cwd(?config(priv_dir, Config)),
+
+ File = "t.erl",
+ Test = <<"-module(t).
+ -export([t/0]).
+ -include_lib(\"stdlib/include/ms_transform.hrl\").
+ t() ->
+ ets:fun2ms(fun(X) -> X end).
+ ">>,
+ ?line ok = file:write_file(File, Test),
+ ?line {ok, t} = cover:compile(File),
+ ?line cover:stop(),
+ ?line ok = file:delete(File),
+
+ ok.
+
+otp_5418(suite) -> [];
+otp_5418(Config) when is_list(Config) ->
+ ?line ok = file:set_cwd(?config(priv_dir, Config)),
+
+ File = "t.erl",
+ Test = <<"-module(t).
+ ">>,
+ ?line ok = file:write_file(File, Test),
+ ?line {ok, t} = cover:compile(File),
+ ?line {ok,{t,{0,0}}} = cover:analyse(t, module),
+ ?line cover:stop(),
+ ?line ok = file:delete(File),
+
+ ok.
+
+otp_6115(suite) -> [];
+otp_6115(Config) when is_list(Config) ->
+ case erlang:system_info(heap_type) of
+ hybrid -> {skip,"Hybrid-heap emulator doesn't keep track of funs"};
+ _ -> otp_6115_1(Config)
+ end.
+
+otp_6115_1(Config) ->
+ ?line {ok, CWD} = file:get_cwd(),
+ ?line Dir = filename:join(?config(data_dir, Config), otp_6115),
+ ?line ok = file:set_cwd(Dir),
+ ?line {ok, f1} = compile:file(f1, [debug_info]),
+ ?line {ok, f2} = compile:file(f2, [debug_info]),
+
+ %% Cover compile f1, but not f2
+ ?line {ok, f1} = cover:compile(f1),
+
+ %% If f1 is cover compiled, a process P is started with a
+ %% reference to the fun created in start_fail/0, and cover:stop() is
+ %% called, then P should be killed.
+ %% This is because (the fun held by P) references the cover
+ %% compiled code which should be *unloaded* when cover:stop() is
+ %% called -- running cover compiled code when there is no cover
+ %% server and thus no ets tables to bump counters in, makes no
+ %% sense.
+ ?line Pid1 = f1:start_fail(),
+
+ %% If f1 is cover compiled, a process P is started with a
+ %% reference to the fun created in start_ok/0, and
+ %% cover:stop() is called, then P should survive.
+ %% This is because (the fun held by) P always references the current
+ %% version of the module, and is thus not affected by the cover
+ %% compiled version being unloaded.
+ ?line Pid2 = f1:start_ok(),
+
+ %% Now stop cover
+ ?line cover:stop(),
+
+ %% Ensure that f1 is loaded (and not cover compiled), that Pid1
+ %% is dead and Pid2 is alive, but with no reference to old code
+ case code:which(f1) of
+ Beam when is_list(Beam) ->
+ ok;
+ Other ->
+ ?line ?t:fail({"f1 is not reloaded", Other})
+ end,
+ case process_info(Pid1) of
+ undefined ->
+ ok;
+ _PI1 ->
+ RefToOldP = erlang:check_process_code(Pid1, f1),
+ ?line ?t:fail({"Pid1 still alive", RefToOldP})
+ end,
+ case process_info(Pid2) of
+ PI2 when is_list(PI2) ->
+ case erlang:check_process_code(Pid2, f2) of
+ false ->
+ ok;
+ true ->
+ ?line ?t:fail("Pid2 has ref to old code")
+ end;
+ undefined ->
+ ?line ?t:fail("Pid2 has died")
+ end,
+
+ ?line file:set_cwd(CWD),
+ ok.
+
+otp_7095(doc) ->
+ ["andalso/orelse"];
+otp_7095(suite) -> [];
+otp_7095(Config) when is_list(Config) ->
+ ?line ok = file:set_cwd(?config(priv_dir, Config)),
+
+ File = "t.erl",
+ Test = <<"-module(t).
+ -export([t/0]).
+ t() ->
+ t1(),
+ t2(),
+ t3(),
+ t4(),
+ t5(),
+ put(t6, 0),
+ 0 = t6(),
+ 1 = erase(t6),
+ t7(),
+ put(t8, 0),
+ {'EXIT',{{badarg,0},_}} = (catch t8()),
+ 1 = erase(t8),
+ t9(),
+ ok.
+
+ t1() ->
+ false % 20
+ andalso
+ true. % 22
+
+ t2() ->
+ true % 25
+ andalso
+ true. % 27
+
+ t3() ->
+ false % 30
+ orelse
+ true. % 32
+
+ t4() ->
+ true % 35
+ orelse
+ true. % 37
+
+ t5() ->
+ true % 40
+ andalso
+ true % 42
+ andalso
+ false. % 44
+
+ t6() ->
+ true andalso % 47
+ add_one(t6). % 48
+
+ t7() ->
+ true % 51
+ andalso
+ false % 53
+ andalso
+ not_ok. % 55
+
+ t8() ->
+ true % 58
+ andalso
+ true % 60
+ andalso
+ add_one(t8) % 62
+ andalso
+ false. % 64
+
+ t9() ->
+ if % 67
+ true ->
+ true % 69
+ andalso
+ false % 71
+ end
+ orelse
+ case ok of % 74
+ true ->
+ a; % 76
+ _ ->
+ true % 78
+ end.
+
+ add_one(T) ->
+ put(T, get(T) + 1). % 82
+ ">>,
+ ?line ok = file:write_file(File, Test),
+ ?line {ok, t} = cover:compile(File),
+ ?line ok = t:t(),
+ ?line {ok,[{{t,4},1},{{t,5},1},{{t,6},1},{{t,7},1},{{t,8},1},{{t,9},1},
+ {{t,10},1},{{t,11},1},{{t,12},1},{{t,13},1},{{t,14},1},
+ {{t,15},1},{{t,16},1},{{t,17},1},
+ {{t,20},1},{{t,22},0},
+ {{t,25},1},{{t,27},1},
+ {{t,30},1},{{t,32},1},
+ {{t,35},1},{{t,37},0},
+ {{t,40},1},{{t,42},1},{{t,44},1},
+ {{t,47},1},{{t,48},1},
+ {{t,51},1},{{t,53},1},{{t,55},0},
+ {{t,58},1},{{t,60},1},{{t,62},1},{{t,64},0},
+ {{t,67},1},{{t,69},1},{{t,71},1},{{t,74},1},
+ {{t,76},0},{{t,78},1},
+ {{t,82},2}]} = cover:analyse(t, calls, line),
+ ?line cover:stop(),
+ ?line ok = file:delete(File),
+
+ ok.
+
+otp_8270(doc) ->
+ ["OTP-8270. Bug."];
+otp_8270(suite) -> [];
+otp_8270(Config) when is_list(Config) ->
+ ?line DataDir = ?config(data_dir, Config),
+ ?line ok = file:set_cwd(DataDir),
+
+ ?line PrivDir = ?config(priv_dir, Config),
+
+ As = [{args," -pa " ++ PrivDir}],
+ ?line {ok,N1} = ?t:start_node(cover_n1,slave,As),
+ ?line {ok,N2} = ?t:start_node(cover_n2,slave,As),
+ ?line {ok,N3} = ?t:start_node(cover_n3,slave,As),
+
+ timer:sleep(500),
+ cover:start(nodes()),
+
+ Test = <<
+ "-module(m).\n"
+ "-compile(export_all).\n"
+ "t() -> t(0).\n"
+ "l() ->\n"
+ " catch ets:tab2list(cover_internal_data_table).\n"
+ "t(Sz) ->\n"
+ " case ets:info(cover_internal_data_table, size) of\n"
+ " Sz ->\n"
+ " m:t(Sz); % Not a local call! Newly loaded code is entered.\n"
+ " NSz ->\n"
+ " % error_logger:info_msg(\"~p: ~p ~p change~n L1 ~p~n\", \n"
+ " % [node(), Sz, NSz, l()]),\n"
+ " m:t(NSz)\n"
+ " end.\n">>,
+ ?line _File = c_mod(m, Test, Config),
+ Fun = fun m:t/0,
+ ?line Pid1 = spawn(Fun),
+ ?line Pid2 = spawn(N1, Fun),
+ ?line Pid3 = spawn(N2, Fun),
+ ?line Pid4 = spawn(N3, Fun),
+
+ ?line {ok, m} = cover:compile_beam(m),
+
+ timer:sleep(1000),
+
+ ?line Info = erlang:process_info(Pid1),
+ ?line N1_info = rpc:call(N1, erlang, process_info, [Pid2]),
+ ?line N2_info = rpc:call(N2, erlang, process_info, [Pid3]),
+ ?line N3_info = rpc:call(N3, erlang, process_info, [Pid4]),
+
+ ?line true = is_list(Info),
+ ?line {N1,true} = {N1,is_list(N1_info)},
+ ?line {N2,true} = {N2,is_list(N2_info)},
+ ?line {N3,true} = {N3,is_list(N3_info)},
+
+ ?line ?t:stop_node(N1),
+ ?line ?t:stop_node(N2),
+ ?line ?t:stop_node(N3),
+ ok.
+
+otp_8273(doc) ->
+ ["OTP-8270. Bug."];
+otp_8273(suite) -> [];
+otp_8273(Config) when is_list(Config) ->
+ Test = <<"-module(t).
+ -export([t/0]).
+ t() ->
+ foo = true andalso foo,
+ bar = false orelse bar,
+ ok.
+ ">>,
+ ?line File = cc_mod(t, Test, Config),
+ ?line ok = t:t(),
+ ?line cover:stop(),
+ ?line ok = file:delete(File),
+
+ ok.
+
+otp_8340(doc) ->
+ ["OTP-8340. Bug."];
+otp_8340(suite) -> [];
+otp_8340(Config) when is_list(Config) ->
+ ?line [{{t,1},1},{{t,2},1},{{t,4},1}] =
+ analyse_expr(<<"<< \n"
+ " <<3:2, \n"
+ " SeqId:62>> \n"
+ " || SeqId <- [64] >>">>, Config),
+
+ ok.
+
+otp_8188(doc) ->
+ ["Clauses on the same line."];
+otp_8188(suite) -> [];
+otp_8188(Config) when is_list(Config) ->
+ %% This example covers the bug report:
+ Test = <<"-module(t).
+ -export([test/1]).
+
+ -define(FOOBAR(X),
+ case X of
+ ok -> true;
+ _ -> false
+ end).
+
+ test(X)->
+ _Res =
+ ?FOOBAR(X).
+ ">>,
+ ?line File = cc_mod(t, Test, Config),
+ ?line false = t:test(nok),
+ ?line {ok,[{{t,11},1},{{t,12},1}]} = cover:analyse(t, calls, line),
+ ?line cover:stop(),
+ ?line ok = file:delete(File),
+
+ %% Bit string comprehensions are now traversed;
+ %% the handling of list comprehensions has been improved:
+ comprehension_8188(Config),
+
+ %% Variants of the reported bug:
+ bug_8188(Config),
+ ok.
+
+bug_8188(Cf) ->
+ ?line [{{t,1},1},{{t,2},1},{{t,3},1}] =
+ analyse_expr(<<"A = 3,\n" % 1
+ " case A of\n" % 1
+ " 2 -> two; 3 -> three end, A + 2">>, % 1
+ Cf),
+
+ ?line [{{t,1},1},
+ {{t,2},0},
+ {{t,3},1},
+ {{t,4},1},
+ {{t,5},1},
+ {{t,6},0},
+ {{t,7},1},
+ {{t,9},2}] =
+ analyse_expr(<<"case two() of\n" % 1
+ " 1 -> 2;\n" % 0
+ " _ -> begin 3 end\n" % 1
+ " +\n" % 1
+ " begin 4 end end, case two() of\n" % 1
+ " 1 -> a;\n" % 0
+ " 2 -> b; 3 -> c\n" % 1
+ " end.\n"
+ "two() -> 2">>, Cf), % 2
+
+ ?line [{{t,1},1}, {{t,2},1}, {{t,3},1},
+ {{t,4},1}, {{t,5},1}, {{t,6},0}] =
+ analyse_expr(<<" self() ! 1,\n"
+ " receive \n"
+ " X=1 -> a;\n"
+ " X=2 -> b end, case X of \n"
+ " 1 -> a;\n"
+ " 2 -> b\n"
+ " end">>, Cf),
+
+ T0 = <<"t1(X) ->\n "
+ "case X of\n"
+ " 1 -> A=a,B=A,B; % bump Li\n"
+ " 2 -> b; 3 -> case X of % 2 -> b shall bump Li\n"
+ " 3 -> a; % bump Li\n"
+ " 2 -> b end; 4 -> d end, case X of % Li\n"
+ " 1 -> a;\n"
+ " 2 -> b; 3 -> c;\n"
+ " 4 -> d\n"
+ " end">>,
+
+ T1 = [<<"a = t1(1). ">>,T0],
+ ?line [{{t,1},1}, {{t,2},1}, {{t,3},1}, {{t,4},0},
+ {{t,5},0}, {{t,6},1}, {{t,7},1}, {{t,8},0}, {{t,9},0}] =
+ analyse_expr(T1, Cf),
+
+ T2 = [<<"b = t1(2). ">>,T0],
+ ?line [{{t,1},1}, {{t,2},1}, {{t,3},0}, {{t,4},1},
+ {{t,5},0}, {{t,6},1}, {{t,7},0}, {{t,8},1}, {{t,9},0}] =
+ analyse_expr(T2, Cf),
+
+ T3 = [<<"c = t1(3). ">>,T0],
+ ?line [{{t,1},1}, {{t,2},1}, {{t,3},0}, {{t,4},1},
+ {{t,5},1}, {{t,6},1}, {{t,7},0}, {{t,8},1}, {{t,9},0}] =
+ analyse_expr(T3, Cf),
+
+ T4 = [<<"d = t1(4). ">>,T0],
+ ?line [{{t,1},1}, {{t,2},1}, {{t,3},0}, {{t,4},0},
+ {{t,5},0}, {{t,6},1}, {{t,7},0}, {{t,8},0}, {{t,9},1}] =
+ analyse_expr(T4, Cf),
+
+ ?line [{{t,1},1},{{t,2},1},{{t,3},1},{{t,4},1},{{t,5},1}] =
+ analyse_expr(
+ <<"2 = x3(1). "
+ "x3(X) ->\n"
+ " case X of \n"
+ " 1 -> case X of\n"
+ " 1 -> a, Y = 2;\n"
+ " 2 -> b, Y = 3 end, Y; 2 -> Y = 4 end, Y">>, Cf),
+
+ ?line [{{t,1},1},{{t,2},1},{{t,3},1},{{t,4},1}] =
+ analyse_expr(
+ <<"1 = x4(1). "
+ "x4(X) ->\n"
+ " case X of\n"
+ " 1 -> case X of\n"
+ " 1 -> Y = 1 end, case X of 1 -> Y = 1 end, Y end">>,
+ Cf),
+
+ T10 = <<"t1(X) ->\n"
+ "if\n"
+ " X =:= 1 -> a;\n"
+ " X =:= 2 -> b; X =:= 3 -> c end, case X of \n"
+ " 1 -> a;\n"
+ " 2 -> b; 3 -> c end, case X of\n"
+ " 1 -> a;\n"
+ " 2 -> b; 3 -> c\n"
+ " end">>,
+ T11 = [<<"a = t1(1). ">>,T10],
+ ?line [{{t,1},1}, {{t,2},1}, {{t,3},1}, {{t,4},1},
+ {{t,5},1}, {{t,6},1}, {{t,7},1}, {{t,8},0}] =
+ analyse_expr(T11, Cf),
+
+ T12 = [<<"b = t1(2). ">>,T10],
+ ?line [{{t,1},1}, {{t,2},1}, {{t,3},0}, {{t,4},1},
+ {{t,5},0}, {{t,6},1}, {{t,7},0}, {{t,8},1}] =
+ analyse_expr(T12, Cf),
+
+ T13 = [<<"c = t1(3). ">>,T10],
+ ?line [{{t,1},1}, {{t,2},1}, {{t,3},0}, {{t,4},1},
+ {{t,5},0}, {{t,6},1}, {{t,7},0}, {{t,8},1}] =
+ analyse_expr(T13, Cf),
+
+ T20 = <<"t1(X) ->\n"
+ "case X of\n"
+ " 1 -> a;\n"
+ " 2 -> b; 3 -> case X of\n"
+ " 1 -> a;\n"
+ " 2 -> b; 3 -> c end end, case X of\n"
+ " 1 -> a;\n"
+ " 2 -> b; 3 -> c\n"
+ " end">>,
+
+ T21 = [<<"a = t1(1). ">>,T20],
+ ?line [{{t,1},1}, {{t,2},1}, {{t,3},1}, {{t,4},0},
+ {{t,5},0}, {{t,6},1}, {{t,7},1}, {{t,8},0}] =
+ analyse_expr(T21, Cf),
+
+ T22 = [<<"b = t1(2). ">>,T20],
+ ?line [{{t,1},1}, {{t,2},1}, {{t,3},0}, {{t,4},1},
+ {{t,5},0}, {{t,6},1}, {{t,7},0}, {{t,8},1}] =
+ analyse_expr(T22, Cf),
+
+ T23 = [<<"c = t1(3). ">>,T20],
+ ?line [{{t,1},1}, {{t,2},1}, {{t,3},0}, {{t,4},1},
+ {{t,5},0}, {{t,6},1}, {{t,7},0}, {{t,8},1}] =
+ analyse_expr(T23, Cf),
+
+ T30 = <<
+ "t1(X) ->\n"
+ "case X of\n"
+ " 1 -> a;\n"
+ " 2 -> b; 3 -> case X of 1 -> a; 2 -> b; 3 -> c end end, case X of\n"
+ " 1 -> a;\n"
+ " 2 -> b; 3 -> c\n"
+ " end\n">>,
+
+ T31 = [<<"a = t1(1). ">>,T30],
+ ?line [{{t,1},1}, {{t,2},1}, {{t,3},1},
+ {{t,4},1}, {{t,5},1}, {{t,6},0}] =
+ analyse_expr(T31, Cf),
+
+ T32 = [<<"b = t1(2). ">>,T30],
+ ?line [{{t,1},1}, {{t,2},1}, {{t,3},0},
+ {{t,4},1}, {{t,5},0}, {{t,6},1}] =
+ analyse_expr(T32, Cf),
+
+ T33 = [<<"c = t1(3). ">>,T30],
+ ?line [{{t,1},1}, {{t,2},1}, {{t,3},0},
+ {{t,4},1}, {{t,5},0}, {{t,6},1}] =
+ analyse_expr(T33, Cf),
+
+ %% 'try' now traverses the body as a body...
+ ?line [{{t,1},1},{{t,2},1},{{t,3},1},{{t,4},0},{{t,6},1}] =
+ analyse_expr(<<"try \n"
+ " B = 2, \n"
+ " C = erlang:error(foo), \n"
+ " {B,C} \n"
+ "catch _:_ -> \n"
+ " foo \n"
+ "end">>, Cf),
+
+ %% receive after:
+ ?line [{{t,1},1},{{t,2},0},{{t,3},1}] =
+ analyse_expr(<<"receive \n"
+ " X=1 -> a; \n"
+ " X=2 -> b after begin 10 end -> X=3 end">>, Cf),
+ ?line [{{t,1},1},{{t,2},0},{{t,3},1}] =
+ analyse_expr(<<"receive \n"
+ " X=1 -> a; \n"
+ " X=2 -> b after 10 -> begin X=3 end end">>, Cf),
+ ok.
+
+comprehension_8188(Cf) ->
+ ?line [{{t,1},1}] =
+ analyse_expr(<<"[begin X end || X <- [1,2,3], X > 1]">>, Cf),
+ ?line [{{t,1},1},{{t,2},1}] =
+ analyse_expr(<<"[begin X end || \n"
+ " X <- [1,2,3], X > 1]">>, Cf),
+ ?line [{{t,1},1},{{t,2},1},{{t,3},3}] =
+ analyse_expr(<<"[begin X end || \n "
+ " X <- [1,2,3], \n "
+ " X > 1]">>, Cf),
+ ?line [{{t,1},1},{{t,3},1},{{t,4},3}] =
+ analyse_expr(<<"[begin X end || \n "
+ " X <- \n "
+ " [1,2,3], \n "
+ " X > 1]">>, Cf),
+ ?line [{{t,1},1},{{t,2},2}] =
+ analyse_expr(<<"[ \n "
+ " X || X <- [1,2,3], X > 1]">>, Cf),
+ ?line [{{t,1},1},{{t,2},2},{{t,3},3}] =
+ analyse_expr(<<"[ \n"
+ " X || X <- [1,2,3], \n"
+ " X > 1]">>, Cf),
+ ?line [{{t,1},1},{{t,2},1},{{t,3},2}] =
+ analyse_expr(<<"[ \n "
+ " X || X <- [1,2,3], X > 1, \n"
+ " X > 2]">>, Cf),
+
+ ?line [{{t,1},1},
+ {{t,3},2},
+ {{t,5},1},
+ {{t,7},1},
+ {{t,8},0},
+ {{t,12},3},
+ {{t,15},2},
+ {{t,17},2},
+ {{t,18},1}] =
+ analyse_expr(<<"[ \n" % 1
+ " begin\n"
+ " X * 2\n" % 2
+ " end ||\n"
+ " X <- [1,\n" % 1
+ " case two() of\n"
+ " 2 -> 2;\n" % 1
+ " _ -> two\n" % 0
+ " end,\n"
+ " 3],\n"
+ " begin\n"
+ " math:sqrt(X) > 1.0\n" % 3
+ " end,\n"
+ " begin\n"
+ " true\n" % 2
+ " end,\n"
+ " true]. \n" % 2
+ " two() -> 2">>, Cf), % 1
+
+ ?line [{{t,1},1},
+ {{t,2},2},
+ {{t,3},1},
+ {{t,5},1},
+ {{t,6},0},
+ {{t,9},3},
+ {{t,10},2},
+ {{t,11},2},
+ {{t,12},1}] =
+ analyse_expr(<<"[ \n"
+ " X * 2 || \n" % 2
+ " X <- [1,\n" % 1
+ " case two() of\n"
+ " 2 -> 2;\n" % 1
+ " _ -> two\n" % 0
+ " end,\n"
+ " 3],\n"
+ " math:sqrt(X) > 1.0,\n" % 3
+ " true,\n" % 2
+ " true]. \n" % 2
+ " two() -> 2">>, Cf), % 1
+
+ ?line [{{t,1},1},
+ {{t,2},2},
+ {{t,3},1},
+ {{t,4},1},
+ {{t,5},0},
+ {{t,8},1},
+ {{t,9},0},
+ {{t,12},3},
+ {{t,13},2},
+ {{t,14},2}] =
+ analyse_expr(<<"<< \n" % 1
+ " << (X*2) >> || \n" % 2
+ " <<X>> <= << (case two() of\n"
+ " 2 -> 1;\n" % 1
+ " _ -> 2\n" % 0
+ " end)/integer,\n"
+ " (case two() of \n"
+ " 2 -> 2;\n" % 1
+ " _ -> two\n" % 0
+ " end)/integer,\n"
+ " 3 >>, \n"
+ " math:sqrt(X) > 1.0,\n" % 3
+ " true >>.\n" % 2
+ "two() -> 2">>, Cf),
+
+ ?line [{{t,1},1},
+ {{t,2},4},
+ {{t,4},1},
+ {{t,6},1},
+ {{t,7},0},
+ {{t,10},3},
+ {{t,11},2},
+ {{t,12},4},
+ {{t,13},1}] =
+ analyse_expr(<<"<< \n" % 1
+ " << (2)\n" % 4
+ " :(8) >> || \n"
+ " <<X>> <= << 1,\n" % 1
+ " (case two() of \n"
+ " 2 -> 2;\n" % 1
+ " _ -> two\n" % 0
+ " end)/integer,\n"
+ " 3 >>, \n"
+ " math:sqrt(X) > 1.0,\n" % 3
+ " <<_>> <= << 1, 2 >>,\n" % 2
+ " true >>.\n" % 4
+ "two() -> 2">>, Cf), % 1
+
+ ok.
+
+%%--Auxiliary------------------------------------------------------------
+
+analyse_expr(Expr, Config) ->
+ Binary = [<<"-module(t). "
+ "-export([t/0]). "
+ "t() -> ">>, Expr, <<".\n">>],
+ File = cc_mod(t, Binary, Config),
+ t:t(),
+ {ok, Result} = cover:analyse(t, calls, line),
+ ok = file:delete(File),
+ Result.
+
+cc_mod(M, Binary, Config) ->
+ {ok, Dir} = file:get_cwd(),
+ PrivDir = ?config(priv_dir, Config),
+ ok = file:set_cwd(PrivDir),
+ File = atom_to_list(M) ++ ".erl",
+ try
+ ok = file:write_file(File, Binary),
+ {ok, M} = cover:compile(File),
+ filename:join(PrivDir, File)
+ after file:set_cwd(Dir)
+ end.
+
+c_mod(M, Binary, Config) ->
+ {ok, Dir} = file:get_cwd(),
+ PrivDir = ?config(priv_dir, Config),
+ ok = file:set_cwd(PrivDir),
+ File = atom_to_list(M) ++ ".erl",
+ try
+ ok = file:write_file(File, Binary),
+ {ok, M} = compile:file(File, [debug_info]),
+ code:purge(M),
+ AbsFile = filename:rootname(File, ".erl"),
+ code:load_abs(AbsFile, M),
+ filename:join(PrivDir, File)
+ after file:set_cwd(Dir)
+ end.
+
+lsfiles() ->
+ {ok, CWD} = file:get_cwd(),
+ lsfiles(CWD).
+
+lsfiles(Dir) ->
+ {ok, Files} = file:list_dir(Dir),
+ Files.
+
+files(Files, Ext) ->
+ lists:filter(fun(File) ->
+ case filename:extension(File) of
+ Ext -> true;
+ _ -> false
+ end
+ end,
+ Files).
+
+remove([File|Files]) ->
+ ok = file:delete(File),
+ remove(Files);
+remove([]) ->
+ ok.
+
+decompile([Mod|Mods]) ->
+ code:purge(Mod),
+ code:delete(Mod),
+ decompile(Mods);
+decompile([]) ->
+ ok.
+
+is_unloaded(What) ->
+ if
+ is_list(What) -> true;
+ What==non_existing -> true;
+ true -> false
+ end.
+
+check_f_calls(F1,F2) ->
+ {ok,[{{f,f1,0},F1},{{f,f2,0},F2}]} = cover:analyse(f,calls,function).
diff --git a/lib/tools/test/cover_SUITE_data/a.erl b/lib/tools/test/cover_SUITE_data/a.erl
new file mode 100644
index 0000000000..31119821cd
--- /dev/null
+++ b/lib/tools/test/cover_SUITE_data/a.erl
@@ -0,0 +1,55 @@
+-module(a).
+-export([start/1, stop/1]).
+-export([pong/1]).
+-export([loop/3,exit_kalle/0]).
+
+%% start(N) -> pid()
+%% N = integer()
+start(N) ->
+ Pong = b:start(),
+ spawn(?MODULE, loop, [self(), N, Pong]),
+ receive
+ done ->
+ {exit,kalle} = trycatch(fun ?MODULE:exit_kalle/0),
+ {throw,kalle} = trycatch(fun() -> throw(kalle) end),
+ done
+ end.
+
+%% stop(Ping) -> stop
+%% Ping = pid()
+stop(Ping) ->
+ Ping ! stop.
+
+%% pong(Ping) -> pong
+%% Ping = pid()
+pong(Ping) ->
+ Ping ! pong.
+
+%%--Internal functions------------------------------------------------
+
+loop(Starter, N, Pong) when N>0 ->
+ Pong ! {ping, self()},
+ receive
+ pong ->
+ loop(Starter, N-1, Pong);
+ stop ->
+ done
+ end;
+loop(Starter, 0, Pong) ->
+ Pong ! stop,
+ Starter ! done.
+
+
+trycatch(Fun) ->
+ try Fun()
+ catch
+ Throw ->
+ {throw,Throw};
+ exit:Reason ->
+ {exit,Reason}
+ after
+ cleanup
+ end.
+
+exit_kalle() ->
+ exit(kalle).
diff --git a/lib/tools/test/cover_SUITE_data/b.erl b/lib/tools/test/cover_SUITE_data/b.erl
new file mode 100644
index 0000000000..13f39b8cb9
--- /dev/null
+++ b/lib/tools/test/cover_SUITE_data/b.erl
@@ -0,0 +1,14 @@
+-module(b).
+-export([start/0, loop/0]).
+
+start() ->
+ spawn(?MODULE, loop, []).
+
+loop() ->
+ receive
+ {ping, Ping} ->
+ a:pong(Ping),
+ loop();
+ stop ->
+ done
+ end.
diff --git a/lib/tools/test/cover_SUITE_data/cc.erl b/lib/tools/test/cover_SUITE_data/cc.erl
new file mode 100644
index 0000000000..587bdbe493
--- /dev/null
+++ b/lib/tools/test/cover_SUITE_data/cc.erl
@@ -0,0 +1,88 @@
+-module(cc).
+-export([epp/1, epp/2, dbg/1, dbg/2, cvr/1, cvr/2]).
+-export([p/2, pp/2]).
+
+%% epp(Module) - Creates Module.epp which contains all forms of Module
+%% as obtained by using epp.
+%%
+%% dbg(Module) - Creates Module.dbg which contains all forms of Module
+%% as obtained by using beam_lib:chunks/2.
+%%
+%% cvr(Module) - Creates Module.cvr which contains all forms of Module
+%% as obtained by using cover:transform/3.
+%%
+
+epp(Module) ->
+ epp(Module, p).
+epp(Module, P) ->
+ File = atom_to_list(Module)++".erl",
+ {ok,Cwd} = file:get_cwd(),
+ {ok, Fd1} = epp:open(File, [Cwd], []),
+ {ok, Fd2} = file:open(atom_to_list(Module)++".epp", write),
+
+ epp(Fd1, Fd2, P),
+
+ epp:close(Fd1),
+ file:close(Fd2),
+ ok.
+
+epp(Fd1, Fd2, P) ->
+ case epp:parse_erl_form(Fd1) of
+ {ok, {attribute,Line,Attr,Data}} ->
+ epp(Fd1, Fd2, P);
+ {ok, Form} when P==p ->
+ io:format(Fd2, "~p.~n", [Form]),
+ epp(Fd1, Fd2, P);
+ {ok, Form} when P==pp ->
+ io:format(Fd2, "~p.~n", [erl_pp:form(Form)]),
+ epp(Fd1, Fd2, P);
+ {eof, Line} ->
+ ok
+ end.
+
+cvr(Module) ->
+ cvr(Module, p).
+cvr(Module, P) ->
+ case beam_lib:chunks(Module, [abstract_code]) of
+ {ok, {Module, [{abstract_code, no_abstract_code}]}} ->
+ {error, {no_debug_info,Module}};
+ {ok, {Module, [{abstract_code, {Vsn, Forms}}]}} ->
+ Vars = {vars,Module,Vsn, [],
+ undefined, undefined, undefined, undefined, undefined,
+ undefined,
+ false},
+ {ok, TForms, _Vars2} = cover:transform(Forms, [], Vars),
+ File = atom_to_list(Module)++".cvr",
+ apply(?MODULE, P, [File, TForms]);
+ Error ->
+ Error
+ end.
+
+dbg(Module) ->
+ dbg(Module, p).
+dbg(Module, P) ->
+ case beam_lib:chunks(Module, [abstract_code]) of
+ {ok, {Module, [{abstract_code, no_abstract_code}]}} ->
+ {error, {no_debug_info,Module}};
+ {ok, {Module, [{abstract_code, {Vsn, Forms}}]}} ->
+ File = atom_to_list(Module)++".dbg",
+ apply(?MODULE, P, [File, Forms]);
+ Error ->
+ Error
+ end.
+
+p(File, Forms) ->
+ {ok, Fd} = file:open(File, write),
+ lists:foreach(fun(Form) ->
+ io:format(Fd, "~p.~n", [Form])
+ end,
+ Forms),
+ file:close(Fd).
+
+pp(File, Forms) ->
+ {ok, Fd} = file:open(File, write),
+ lists:foreach(fun(Form) ->
+ io:format(Fd, "~s", [erl_pp:form(Form)])
+ end,
+ Forms),
+ file:close(Fd).
diff --git a/lib/tools/test/cover_SUITE_data/compile_beam/crypt.erl b/lib/tools/test/cover_SUITE_data/compile_beam/crypt.erl
new file mode 100644
index 0000000000..1596777edf
--- /dev/null
+++ b/lib/tools/test/cover_SUITE_data/compile_beam/crypt.erl
@@ -0,0 +1,6 @@
+-module(crypt).
+
+-export([f/0]).
+
+f() ->
+ ok.
diff --git a/lib/tools/test/cover_SUITE_data/compile_beam/d/y.erl b/lib/tools/test/cover_SUITE_data/compile_beam/d/y.erl
new file mode 100644
index 0000000000..14b9461410
--- /dev/null
+++ b/lib/tools/test/cover_SUITE_data/compile_beam/d/y.erl
@@ -0,0 +1,6 @@
+-module(y).
+
+-export([f/0]).
+
+f() ->
+ ok.
diff --git a/lib/tools/test/cover_SUITE_data/compile_beam/v.erl b/lib/tools/test/cover_SUITE_data/compile_beam/v.erl
new file mode 100644
index 0000000000..007957297a
--- /dev/null
+++ b/lib/tools/test/cover_SUITE_data/compile_beam/v.erl
@@ -0,0 +1,6 @@
+-module(v).
+
+-export([f/0]).
+
+f() ->
+ ok.
diff --git a/lib/tools/test/cover_SUITE_data/compile_beam/w.erl b/lib/tools/test/cover_SUITE_data/compile_beam/w.erl
new file mode 100644
index 0000000000..88ad606db8
--- /dev/null
+++ b/lib/tools/test/cover_SUITE_data/compile_beam/w.erl
@@ -0,0 +1,6 @@
+-module(w).
+
+-export([f/0]).
+
+f() ->
+ ok.
diff --git a/lib/tools/test/cover_SUITE_data/compile_beam/x.erl b/lib/tools/test/cover_SUITE_data/compile_beam/x.erl
new file mode 100644
index 0000000000..8953f6d05d
--- /dev/null
+++ b/lib/tools/test/cover_SUITE_data/compile_beam/x.erl
@@ -0,0 +1,6 @@
+-module(x).
+
+-export([f/0]).
+
+f() ->
+ ok.
diff --git a/lib/tools/test/cover_SUITE_data/d.erl b/lib/tools/test/cover_SUITE_data/d.erl
new file mode 100644
index 0000000000..696e27e49b
--- /dev/null
+++ b/lib/tools/test/cover_SUITE_data/d.erl
@@ -0,0 +1,156 @@
+-module(d).
+
+-export([start/0, stop/0]).
+-export([store/2, store/3, move/2,
+ location/1, who_are_at/1, who_are_older/1,
+ size/0]).
+-export([init/0]). % spawn
+
+-record(person, {name, age, location, moved=false}).
+
+%%%----------------------------------------------------------------------
+%%% User interface functions
+%%%----------------------------------------------------------------------
+
+%%% start() -> pid()
+start() ->
+ spawn(?MODULE, init, []).
+
+%%% stop()
+stop() ->
+ arne ! stop.
+
+%%% store(Name, Location) ->
+%%% store(Name, Age, Location) -> ok | {error,Reason}
+%%% Name = Location = atom()
+%%% Age = integer()
+%%% Reason = not_started | no_response | {internal_error,term()}
+store(Name, Location) ->
+ store(Name, ?AGE, Location).
+store(Name, Age, Location) when atom(Name), integer(Age), atom(Location) ->
+ send({store, Name, Age, Location}).
+
+%%% move(OldLocation, NewLocation) -> Names | {error,Reason}
+%%% OldLocation = NewLocation = atom()
+%%% Names = [Name]
+%%% Name = atom()
+%%% Reason = not_started | no_response | {internal_error,term()}
+move(OldLocation, NewLocation) ->
+ send({move, OldLocation, NewLocation}).
+
+%%% location(Name) -> Location | no_such_person | {error,Reason}
+%%% Name = atom()
+%%% Reason = not_started | no_response | {internal_error,term()}
+location(Name) when atom(Name) ->
+ send({location, Name}).
+
+%%% who_are_at(Location) -> Names | {error,Reason}
+%%% Location = atom()
+%%% Names = [Name]
+%%% Name = atom()
+%%% Reason = not_started | no_response | {internal_error,term()}
+who_are_at(Location) when atom(Location) ->
+ send({who_are_at, Location}).
+
+%%% who_are_older(Age) -> Names | {error,Reason}
+%%% Age = integer()
+%%% Names = [Name]
+%%% Name = atom()
+%%% Reason = not_started | no_response | {internal_error,term()}
+who_are_older(Age) when integer(Age) ->
+ send({who_are_older, Age}).
+
+%%% size() -> N | {error,Reason}
+%%% N = integer()
+%%% Reason = not_started | no_response | {internal_error,term()}
+size() ->
+ send(size).
+
+%%%----------------------------------------------------------------------
+%%% Main loop
+%%%----------------------------------------------------------------------
+send(Request) ->
+ Pid = whereis(arne),
+ if
+ Pid==undefined ->
+ {error, not_started};
+ true ->
+ send(Pid, Request)
+ end.
+send(Pid, Request) ->
+ Pid ! {request, self(), Request},
+ receive
+ {reply, Reply} ->
+ Reply
+ after
+ 1000 ->
+ {error, no_response}
+ end.
+
+init() ->
+ register(arne, self()),
+ loop([]).
+
+loop(Db) ->
+ receive
+ stop ->
+ true;
+ {request, From, Request} ->
+ case catch handle(Request, Db) of
+ {reply, Reply, NewDb} ->
+ From ! {reply, Reply},
+ loop(NewDb);
+ {'EXIT', Reason} ->
+ From ! {reply, {error, {internal_error, Reason}}},
+ loop(Db)
+ end
+ end.
+
+%%%----------------------------------------------------------------------
+%%% DB functionality
+%%%----------------------------------------------------------------------
+handle({store, Name, Age, Location}, Db) ->
+ {reply, ok, [#person{name=Name, age=Age, location=Location} | Db]};
+handle({move, OldLocation, NewLocation}, Db) ->
+ {Names, NewDb} = move(OldLocation, NewLocation, Db, [], []),
+ {reply, Names, NewDb};
+handle({location, Name}, Db) ->
+ case lists:keysearch(Name, #person.name, Db) of
+ {value, #person{location=Location}} when atom(Location) ->
+ {reply, Location, Db};
+ false ->
+ {reply, no_such_name, Db}
+ end;
+handle({who_are_at, Location}, Db) ->
+ Result = lists:foldl(fun(Person, Names) ->
+ case Person#person.location of
+ Location ->
+ [Person#person.name | Names];
+ _OtherLocation ->
+ Names
+ end
+ end,
+ [],
+ Db),
+ {reply, Result, Db};
+handle({who_are_older, Old}, Db) ->
+ Result = [Name || {person,Name,Age,Location} <- Db,
+ Age>Old],
+ {reply, Result, Db};
+handle(size, Db) ->
+ Result = count(Db, 0), {reply, Result, Db}.
+
+count([H|T], N) ->
+ count(T, N+1);
+count([], N) ->
+ N.
+
+move(OldLoc, NewLoc, [#person{location=OldLoc} = Person|T], Db, Names) ->
+ NewPerson = Person#person{location=NewLoc,
+ moved=true},
+ NewNames = [Person#person.name|Names],
+ move(OldLoc, NewLoc, T, [NewPerson|Db], NewNames);
+move(OldLoc, NewLoc, [Person|T], Db, Names) ->
+ move(OldLoc, NewLoc, T, [Person|Db], Names);
+move(OldLoc, NewLoc, [], Db, Names) ->
+ {lists:reverse(Names), lists:reverse(Db)}.
diff --git a/lib/tools/test/cover_SUITE_data/d1/e.erl b/lib/tools/test/cover_SUITE_data/d1/e.erl
new file mode 100644
index 0000000000..b4041d48e6
--- /dev/null
+++ b/lib/tools/test/cover_SUITE_data/d1/e.erl
@@ -0,0 +1,127 @@
+-module(e).
+-behaviour(gen_server).
+
+%% External exports
+-export([start_link/0]).
+-export([hello/0]).
+
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
+ code_change/3]).
+
+-record(state, {}).
+
+%%%----------------------------------------------------------------------
+%%% API
+%%%----------------------------------------------------------------------
+start_link() ->
+ gen_server:start_link({local, myserver}, myserver, [], []).
+
+hello() ->
+ gen_server:call(myserver, hello).
+
+%%%----------------------------------------------------------------------
+%%% Callback functions from gen_server
+%%%----------------------------------------------------------------------
+
+%%----------------------------------------------------------------------
+%% Func: init/1
+%% Returns: {ok, State} |
+%% {ok, State, Timeout} |
+%% ignore |
+%% {stop, Reason}
+%%----------------------------------------------------------------------
+init([]) ->
+ {ok, #state{}}.
+
+%%----------------------------------------------------------------------
+%% Func: handle_call/3
+%% Returns: {reply, Reply, State} |
+%% {reply, Reply, State, Timeout} |
+%% {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, Reply, State} | (terminate/2 is called)
+%% {stop, Reason, State} (terminate/2 is called)
+%%----------------------------------------------------------------------
+handle_call(Request, From, State) ->
+ Reply = case Request of
+ char ->
+ $B;
+ integer ->
+ 17;
+ float ->
+ 32.76;
+ string ->
+ "hi there";
+ atom ->
+ hello;
+ block ->
+ begin
+ a,
+ b
+ end;
+ binary ->
+ <<1, 2, 3>>
+ end,
+ {reply, Reply, State}.
+
+%%----------------------------------------------------------------------
+%% Func: handle_cast/2
+%% Returns: {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State} (terminate/2 is called)
+%%----------------------------------------------------------------------
+handle_cast(Msg, State) when atom(Msg) ->
+ {noreply, State};
+handle_cast(Msg, State) when binary(Msg) ->
+ {noreply, State};
+handle_cast(Msg, State) when not is_tuple(Msg), not is_list(Msg) ->
+ {noreply, State};
+handle_cast(Msg, State) when float(Msg) ->
+ {noreply, State};
+handle_cast(Msg, State) when function(Msg) ->
+ {noreply, State};
+handle_cast(Msg, State) when integer(Msg) ->
+ {noreply, State};
+handle_cast(Msg, State) when list(Msg) ->
+ {noreply, State};
+handle_cast(Msg, State) when number(Msg) ->
+ {noreply, State};
+handle_cast(Msg, State) when pid(Msg) ->
+ {noreply, State};
+handle_cast(Msg, State) when port(Msg) ->
+ {noreply, State};
+handle_cast(Msg, State) when reference(Msg) ->
+ {noreply, State};
+handle_cast(Msg, State) when tuple(Msg) ->
+ {noreply, State}.
+
+%%----------------------------------------------------------------------
+%% Func: handle_info/2
+%% Returns: {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State} (terminate/2 is called)
+%%----------------------------------------------------------------------
+handle_info(Info, State) ->
+ {noreply, State}.
+
+%%----------------------------------------------------------------------
+%% Func: terminate/2
+%% Purpose: Shutdown the server
+%% Returns: any (ignored by gen_server)
+%%----------------------------------------------------------------------
+terminate(Reason, State) ->
+ ok.
+
+%%----------------------------------------------------------------------
+%% Func: code_change/3
+%% Purpose: Convert process state when code is changed
+%% Returns: {ok, NewState}
+%%----------------------------------------------------------------------
+code_change(OldVsn, State, Extra) ->
+ {ok, State}.
+
+%%%----------------------------------------------------------------------
+%%% Internal functions
+%%%----------------------------------------------------------------------
+
diff --git a/lib/tools/test/cover_SUITE_data/f.erl b/lib/tools/test/cover_SUITE_data/f.erl
new file mode 100644
index 0000000000..1ef8bbdb49
--- /dev/null
+++ b/lib/tools/test/cover_SUITE_data/f.erl
@@ -0,0 +1,10 @@
+-module(f).
+-export([f1/0,f2/0]).
+
+f1() ->
+ f1_line1,
+ f1_line2.
+
+f2() ->
+ f2_line1,
+ f2_line2.
diff --git a/lib/tools/test/cover_SUITE_data/included_functions/cover_inc.erl b/lib/tools/test/cover_SUITE_data/included_functions/cover_inc.erl
new file mode 100644
index 0000000000..fa8eebfd00
--- /dev/null
+++ b/lib/tools/test/cover_SUITE_data/included_functions/cover_inc.erl
@@ -0,0 +1,8 @@
+-module(cover_inc).
+-compile(export_all).
+-include("cover_inc.hrl").
+
+func() ->
+ func1(),
+ ok.
+
diff --git a/lib/tools/test/cover_SUITE_data/included_functions/cover_inc.hrl b/lib/tools/test/cover_SUITE_data/included_functions/cover_inc.hrl
new file mode 100644
index 0000000000..cbdfe601d1
--- /dev/null
+++ b/lib/tools/test/cover_SUITE_data/included_functions/cover_inc.hrl
@@ -0,0 +1,7 @@
+func1() ->
+ A = line_2_in_include_file,
+ erlang:display(A),
+ line_4_in_include_file.
+
+
+
diff --git a/lib/tools/test/cover_SUITE_data/otp_6115/f1.erl b/lib/tools/test/cover_SUITE_data/otp_6115/f1.erl
new file mode 100644
index 0000000000..b659e5d818
--- /dev/null
+++ b/lib/tools/test/cover_SUITE_data/otp_6115/f1.erl
@@ -0,0 +1,12 @@
+-module(f1).
+-export([start_fail/0, start_ok/0]).
+
+start_fail() ->
+ f2:start(fun() ->
+ io:format("this does not work\n",[])
+ end).
+
+start_ok() ->
+ f2:start(fun fun1/0).
+fun1() ->
+ io:format("this works\n",[]).
diff --git a/lib/tools/test/cover_SUITE_data/otp_6115/f2.erl b/lib/tools/test/cover_SUITE_data/otp_6115/f2.erl
new file mode 100644
index 0000000000..72a6a64c4d
--- /dev/null
+++ b/lib/tools/test/cover_SUITE_data/otp_6115/f2.erl
@@ -0,0 +1,13 @@
+-module(f2).
+-export([start/1]).
+
+start(Fun) ->
+ spawn(fun() ->
+ wait(Fun)
+ end).
+
+wait(Fun) ->
+ receive
+ go ->
+ Fun()
+ end.
diff --git a/lib/tools/test/cprof_SUITE.erl b/lib/tools/test/cprof_SUITE.erl
new file mode 100644
index 0000000000..e697cc1571
--- /dev/null
+++ b/lib/tools/test/cprof_SUITE.erl
@@ -0,0 +1,309 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2002-2010. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%
+%%% Define to run outside of test server
+%%%
+%%% -define(STANDALONE,1).
+%%%
+%%%
+%%% Define for debug output
+%%%
+%%% -define(debug,1).
+
+-module(cprof_SUITE).
+
+%% Exported end user tests
+-export([basic_test/0, on_load_test/1, modules_test/1]).
+
+%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Test server related stuff
+%%
+
+-ifdef(STANDALONE).
+-define(config(A,B),config(A,B)).
+-export([config/2]).
+-else.
+-include("test_server.hrl").
+-endif.
+
+-ifdef(debug).
+-ifdef(STANDALONE).
+-define(line, erlang:display({?MODULE,?LINE}), ).
+-endif.
+-define(dbgformat(A,B),io:format(A,B)).
+-else.
+-ifdef(STANDALONE).
+-define(line, noop, ).
+-endif.
+-define(dbgformat(A,B),noop).
+-endif.
+
+-ifdef(STANDALONE).
+config(priv_dir, _) ->
+ ".";
+config(data_dir, _) ->
+ "cprof_SUITE_data".
+-else.
+%% When run in test server.
+-export([all/1, init_per_testcase/2, fin_per_testcase/2, not_run/1]).
+-export([basic/1, on_load/1, modules/1]).
+
+init_per_testcase(_Case, Config) ->
+ ?line Dog=test_server:timetrap(test_server:seconds(30)),
+ [{watchdog, Dog}|Config].
+
+fin_per_testcase(_Case, Config) ->
+ erlang:trace_pattern({'_','_','_'}, false, [local,meta,call_count]),
+ erlang:trace_pattern(on_load, false, [local,meta,call_count]),
+ erlang:trace(all, false, [all]),
+ Dog=?config(watchdog, Config),
+ test_server:timetrap_cancel(Dog),
+ ok.
+
+all(doc) ->
+ ["Test the cprof profiling tool."];
+all(suite) ->
+ case test_server:is_native(?MODULE) of
+ true -> [not_run];
+ false -> [basic, on_load, modules]
+%, on_and_off, info,
+% pause_and_restart, combo]
+ end.
+
+not_run(Config) when is_list(Config) ->
+ {skipped,"Native code"}.
+
+basic(suite) ->
+ [];
+basic(doc) ->
+ ["Tests basic profiling"];
+basic(Config) when is_list(Config) ->
+ basic_test().
+
+on_load(suite) ->
+ [];
+on_load(doc) ->
+ ["Tests profiling of unloaded module"];
+on_load(Config) when is_list(Config) ->
+ on_load_test(Config).
+
+modules(suite) ->
+ [];
+modules(doc) ->
+ ["Tests profiling of several modules"];
+modules(Config) when is_list(Config) ->
+ modules_test(Config).
+
+-endif. %-ifdef(STANDALONE). ... -else.
+
+%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%% The Tests
+%%%
+
+basic_test() ->
+ ?line M = 1000,
+ %%
+ ?line M2 = M*2,
+ ?line M3 = M*3,
+ ?line M2__1 = M2 + 1,
+ ?line M3__1 = M3 + 1,
+ ?line N = cprof:stop(),
+ %%
+ ?line 2 = cprof:start(?MODULE, seq_r),
+ ?line 1 = cprof:start(?MODULE, seq, 3),
+ ?line L = seq(1, M, fun succ/1),
+ ?line Lr = seq_r(1, M, fun succ/1),
+ ?line L = lists:reverse(Lr),
+ %%
+ ?line io:format("~p~n~p~n~p~n",
+ [erlang:trace_info({?MODULE,sec_r,3}, all),
+ erlang:trace_info({?MODULE,sec_r,4}, all),
+ erlang:trace_info({?MODULE,sec,3}, all)]),
+ %%
+ ?line ModAna1 = {?MODULE,M2__1,[{{?MODULE,seq_r,4},M},
+ {{?MODULE,seq,3},M},
+ {{?MODULE,seq_r,3},1}]},
+ ?line ModAna1 = cprof:analyse(?MODULE,0),
+ ?line {M2__1, [ModAna1]} = cprof:analyse(),
+ ?line ModAna1 = cprof:analyse(?MODULE, 1),
+ ?line {M2__1, [ModAna1]} = cprof:analyse(1),
+ %%
+ ?line ModAna2 = {?MODULE,M2__1,[{{?MODULE,seq_r,4},M},
+ {{?MODULE,seq,3},M}]},
+ ?line ModAna2 = cprof:analyse(?MODULE, 2),
+ ?line {M2__1, [ModAna2]} = cprof:analyse(2),
+ %%
+ 2 = cprof:pause(?MODULE, seq_r),
+ ?line L = seq(1, M, fun succ/1),
+ ?line Lr = seq_r(1, M, fun succ/1),
+ %%
+ ?line ModAna3 = {?MODULE,M3__1,[{{?MODULE,seq,3},M2},
+ {{?MODULE,seq_r,4},M},
+ {{?MODULE,seq_r,3},1}]},
+ ?line ModAna3 = cprof:analyse(?MODULE),
+ %%
+ ?line N = cprof:pause(),
+ ?line L = seq(1, M, fun succ/1),
+ ?line Lr = seq_r(1, M, fun succ/1),
+ %%
+ ?line {M3__1, [ModAna3]} = cprof:analyse(),
+ %%
+ ?line N = cprof:restart(),
+ ?line L = seq(1, M, fun succ/1),
+ ?line Lr = seq_r(1, M, fun succ/1),
+ %%
+ ?line ModAna1 = cprof:analyse(?MODULE),
+ %%
+ ?line N = cprof:stop(),
+ ?line {?MODULE,0,[]} = cprof:analyse(?MODULE),
+ ?line {0,[]} = cprof:analyse(),
+ ok.
+
+%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+on_load_test(Config) ->
+ ?line Priv = ?config(priv_dir, Config),
+ ?line Data = ?config(data_dir, Config),
+ ?line File = filename:join(Data, "cprof_SUITE_test"),
+ ?line Module = cprof_SUITE_test,
+ ?line M = 1000,
+ %%
+ ?line M2 = M*2,
+ ?line M2__1 = M2 + 1,
+ ?line N1 = cprof:start(),
+
+ ?line {ok,Module} = c:c(File, [{outdir,Priv}]),
+
+ %% If this system is hipe-enabled, the loader may have called module_info/1
+ %% when Module was loaded above. Reset the call count to avoid seeing
+ %% the call in the analysis below.
+
+ ?line 1 = cprof:restart(Module, module_info, 1),
+
+ ?line L = Module:seq(1, M, fun succ/1),
+ ?line Lr = Module:seq_r(1, M, fun succ/1),
+ ?line Lr = lists:reverse(L),
+ ?line N2 = cprof:pause(),
+ ?line N3 = cprof:pause(Module),
+ ?line {Module,M2__1,[{{Module,seq_r,4},M},
+ {{Module,seq,3},M},
+ {{Module,seq_r,3},1}]} = cprof:analyse(Module),
+ ?line io:format("~p ~p ~p~n", [N1, N2, N3]),
+ ?line code:purge(Module),
+ ?line code:delete(Module),
+ ?line N4 = N2 - N3,
+ %%
+ ?line N4 = cprof:restart(),
+ ?line {ok,Module} = c:c(File, [{outdir,Priv}]),
+ ?line L = Module:seq(1, M, fun succ/1),
+ ?line Lr = Module:seq_r(1, M, fun succ/1),
+ ?line L = seq(1, M, fun succ/1),
+ ?line Lr = seq_r(1, M, fun succ/1),
+ ?line N2 = cprof:pause(),
+ ?line {Module,0,[]} = cprof:analyse(Module),
+ ?line M_1 = M - 1,
+ ?line M4__4 = M*4 - 4,
+ ?line M10_7 = M*10 - 7,
+ ?line {?MODULE,M10_7,[{{?MODULE,succ,1},M4__4},
+ {{?MODULE,seq_r,4},M},
+ {{?MODULE,seq,3},M},
+ {{?MODULE,'-on_load_test/1-fun-5-',1},M_1},
+ {{?MODULE,'-on_load_test/1-fun-4-',1},M_1},
+ {{?MODULE,'-on_load_test/1-fun-3-',1},M_1},
+ {{?MODULE,'-on_load_test/1-fun-2-',1},M_1},
+ {{?MODULE,seq_r,3},1}]}
+ = cprof:analyse(?MODULE),
+ ?line N2 = cprof:stop(),
+ ok.
+
+%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+modules_test(Config) ->
+ ?line Priv = ?config(priv_dir, Config),
+ ?line Data = ?config(data_dir, Config),
+ ?line File = filename:join(Data, "cprof_SUITE_test"),
+ ?line Module = cprof_SUITE_test,
+ ?line {ok,Module} = c:c(File, [{outdir,Priv}]),
+ ?line M = 10,
+ %%
+ ?line M2 = M*2,
+ ?line M2__1 = M2 + 1,
+ ?line erlang:yield(),
+ ?line N = cprof:start(),
+ ?line L = Module:seq(1, M, fun succ/1),
+ ?line Lr = Module:seq_r(1, M, fun succ/1),
+ ?line L = seq(1, M, fun succ/1),
+ ?line Lr = seq_r(1, M, fun succ/1),
+ ?line N = cprof:pause(),
+ ?line Lr = lists:reverse(L),
+ ?line M_1 = M - 1,
+ ?line M4_4 = M*4 - 4,
+ ?line M10_7 = M*10 - 7,
+ ?line M2__1 = M*2 + 1,
+ ?line {Tot,ModList} = cprof:analyse(),
+ ?line {value,{?MODULE,M10_7,[{{?MODULE,succ,1},M4_4},
+ {{?MODULE,seq_r,4},M},
+ {{?MODULE,seq,3},M},
+ {{?MODULE,'-modules_test/1-fun-3-',1},M_1},
+ {{?MODULE,'-modules_test/1-fun-2-',1},M_1},
+ {{?MODULE,'-modules_test/1-fun-1-',1},M_1},
+ {{?MODULE,'-modules_test/1-fun-0-',1},M_1},
+ {{?MODULE,seq_r,3},1}]}} =
+ lists:keysearch(?MODULE, 1, ModList),
+ ?line {value,{Module,M2__1,[{{Module,seq_r,4},M},
+ {{Module,seq,3},M},
+ {{Module,seq_r,3},1}]}} =
+ lists:keysearch(Module, 1, ModList),
+ ?line Tot = lists:foldl(fun ({_,C,_}, A) -> C+A end, 0, ModList),
+ ?line {cprof,_,Prof} = cprof:analyse(cprof),
+ ?line {value,{{cprof,pause,0},1}} =
+ lists:keysearch({cprof,pause,0}, 1, Prof),
+ ?line N = cprof:stop(),
+ ok.
+
+
+
+%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Local helpers
+
+
+
+%% Stack recursive seq
+seq(Stop, Stop, Succ) when is_function(Succ) ->
+ [Stop];
+seq(Start, Stop, Succ) when is_function(Succ) ->
+ [Start | seq(Succ(Start), Stop, Succ)].
+
+
+
+%% Tail recursive seq, result list is reversed
+seq_r(Start, Stop, Succ) when is_function(Succ) ->
+ seq_r(Start, Stop, Succ, []).
+
+seq_r(Stop, Stop, _, R) ->
+ [Stop | R];
+seq_r(Start, Stop, Succ, R) ->
+ seq_r(Succ(Start), Stop, Succ, [Start | R]).
+
+
+
+%% Successor
+succ(X) -> X+1.
diff --git a/lib/tools/test/cprof_SUITE_data/cprof_SUITE_test.erl b/lib/tools/test/cprof_SUITE_data/cprof_SUITE_test.erl
new file mode 100644
index 0000000000..02d8b027e5
--- /dev/null
+++ b/lib/tools/test/cprof_SUITE_data/cprof_SUITE_test.erl
@@ -0,0 +1,25 @@
+-module(cprof_SUITE_test).
+
+-export([seq/3, seq_r/3]).
+
+
+
+%% Stack recursive seq
+seq(Stop, Stop, Succ) when function(Succ) ->
+ [Stop];
+seq(Start, Stop, Succ) when function(Succ) ->
+ [Start | seq(Succ(Start), Stop, Succ)].
+
+
+
+%% Tail recursive seq, result list is reversed
+seq_r(Start, Stop, Succ) when function(Succ) ->
+ seq_r(Start, Stop, Succ, []).
+
+seq_r(Stop, Stop, _, R) ->
+ [Stop | R];
+seq_r(Start, Stop, Succ, R) ->
+ seq_r(Succ(Start), Stop, Succ, [Start | R]).
+
+
+
diff --git a/lib/tools/test/emem_SUITE.erl b/lib/tools/test/emem_SUITE.erl
new file mode 100644
index 0000000000..430fa86c6c
--- /dev/null
+++ b/lib/tools/test/emem_SUITE.erl
@@ -0,0 +1,713 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2005-2010. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(emem_SUITE).
+
+%%-define(line_trace, 1).
+
+-export([init_per_suite/1, end_per_suite/1,
+ receive_and_save_trace/2, send_trace/2]).
+
+
+-export([all/1, init_per_testcase/2, fin_per_testcase/2]).
+
+-export([live_node/1,
+ 'sparc_sunos5.8_32b_emt2.0'/1,
+ 'pc_win2000_32b_emt2.0'/1,
+ 'pc.smp_linux2.2.19pre17_32b_emt2.0'/1,
+ 'powerpc_darwin7.7.0_32b_emt2.0'/1,
+ 'alpha_osf1v5.1_64b_emt2.0'/1,
+ 'sparc_sunos5.8_64b_emt2.0'/1,
+ 'sparc_sunos5.8_32b_emt1.0'/1,
+ 'pc_win2000_32b_emt1.0'/1,
+ 'powerpc_darwin7.7.0_32b_emt1.0'/1,
+ 'alpha_osf1v5.1_64b_emt1.0'/1,
+ 'sparc_sunos5.8_64b_emt1.0'/1]).
+
+-include_lib("kernel/include/file.hrl").
+
+-include("test_server.hrl").
+
+-define(DEFAULT_TIMEOUT, ?t:minutes(5)).
+
+-define(EMEM_64_32_COMMENT,
+ "64 bit trace; this build of emem can only handle 32 bit traces").
+
+-record(emem_res, {nodename,
+ hostname,
+ pid,
+ start_time,
+ trace_version,
+ max_word_size,
+ word_size,
+ last_values,
+ maximum,
+ exit_code}).
+
+%%
+%%
+%% Exported suite functions
+%%
+%%
+
+all(doc) -> [];
+all(suite) ->
+ case is_debug_compiled() of
+ true -> {skipped, "Not run when debug compiled"};
+ false -> test_cases()
+ end.
+
+test_cases() ->
+ [live_node,
+ 'sparc_sunos5.8_32b_emt2.0',
+ 'pc_win2000_32b_emt2.0',
+ 'pc.smp_linux2.2.19pre17_32b_emt2.0',
+ 'powerpc_darwin7.7.0_32b_emt2.0',
+ 'alpha_osf1v5.1_64b_emt2.0',
+ 'sparc_sunos5.8_64b_emt2.0',
+ 'sparc_sunos5.8_32b_emt1.0',
+ 'pc_win2000_32b_emt1.0',
+ 'powerpc_darwin7.7.0_32b_emt1.0',
+ 'alpha_osf1v5.1_64b_emt1.0',
+ 'sparc_sunos5.8_64b_emt1.0'].
+
+init_per_testcase(Case, Config) when is_list(Config) ->
+ case maybe_skip(Config) of
+ {skip, _}=Skip -> Skip;
+ ok ->
+ Dog = ?t:timetrap(?DEFAULT_TIMEOUT),
+
+ %% Until emem is completely stable we run these tests in a working
+ %% directory with an ignore_core_files file which will make the
+ %% search for core files ignore cores generated by this suite.
+ ignore_cores:setup(?MODULE,
+ Case,
+ [{watchdog, Dog}, {testcase, Case} | Config])
+ end.
+
+fin_per_testcase(_Case, Config) when is_list(Config) ->
+ ignore_cores:restore(Config),
+ Dog = ?config(watchdog, Config),
+ ?t:timetrap_cancel(Dog),
+ ok.
+
+maybe_skip(Config) ->
+ DataDir = ?config(data_dir, Config),
+ case filelib:is_dir(DataDir) of
+ false ->
+ {skip, "No data directory"};
+ true ->
+ case ?config(emem, Config) of
+ undefined ->
+ {skip, "emem not found"};
+ _ ->
+ ok
+ end
+ end.
+
+init_per_suite(Config) when is_list(Config) ->
+ BinDir = filename:join([code:lib_dir(tools), "bin"]),
+ Target = erlang:system_info(system_architecture),
+ Res = (catch begin
+ case check_dir(filename:join([BinDir, Target])) of
+ not_found -> ok;
+ TDir ->
+ check_emem(TDir, purecov),
+ check_emem(TDir, purify),
+ check_emem(TDir, debug),
+ check_emem(TDir, opt)
+ end,
+ check_emem(BinDir, opt),
+ ""
+ end),
+ Res ++ ignore_cores:init(Config).
+
+end_per_suite(Config) when is_list(Config) ->
+ Config1 = lists:keydelete(emem, 1, Config),
+ Config2 = lists:keydelete(emem_comment, 1, Config1),
+ ignore_cores:fini(Config2).
+
+%%
+%%
+%% Test cases
+%%
+%%
+
+live_node(doc) -> [];
+live_node(suite) -> [];
+live_node(Config) when is_list(Config) ->
+ ?line {ok, EmuFlag, Port} = start_emem(Config),
+ ?line Nodename = mk_nodename(Config),
+ ?line {ok, Node} = start_node(Nodename, EmuFlag),
+ ?line NP = spawn(Node,
+ fun () ->
+ receive go -> ok end,
+ I = spawn(fun () -> ignorer end),
+ GC = fun () ->
+ GCP = fun (P) ->
+ garbage_collect(P)
+ end,
+ lists:foreach(GCP, processes())
+ end,
+ Seq = fun () -> I ! lists:seq(1, 1000000) end,
+ spawn_link(Seq),
+ B1 = <<0:10000000>>,
+ spawn_link(Seq),
+ B2 = <<0:10000000>>,
+ spawn_link(Seq),
+ B3 = <<0:10000000>>,
+ I ! {B1, B2, B3},
+ GC(),
+ GC(),
+ GC()
+ end),
+ ?line MRef = erlang:monitor(process, NP),
+ NP ! go,
+ ?line receive
+ {'DOWN', MRef, process, NP, Reason} ->
+ ?line spawn(Node, fun () -> halt(17) end),
+ ?line normal = Reason
+ end,
+ ?line Res = get_emem_result(Port),
+ ?line {ok, Hostname} = inet:gethostname(),
+ ?line ShortHostname = short_hostname(Hostname),
+ ?line {true, _} = has_prefix(Nodename, Res#emem_res.nodename),
+ ?line ShortHostname = short_hostname(Res#emem_res.hostname),
+ ?line Bits = case erlang:system_info(wordsize) of
+ 4 -> ?line "32 bits";
+ 8 -> ?line "64 bits"
+ end,
+ ?line Bits = Res#emem_res.word_size,
+ ?line "17" = Res#emem_res.exit_code,
+ ?line emem_comment(Config).
+
+'sparc_sunos5.8_32b_emt2.0'(doc) -> [];
+'sparc_sunos5.8_32b_emt2.0'(suite) -> [];
+'sparc_sunos5.8_32b_emt2.0'(Config) when is_list(Config) ->
+ ?line Res = run_emem_on_casefile(Config),
+ ?line "test_server" = Res#emem_res.nodename,
+ ?line "gorbag" = Res#emem_res.hostname,
+ ?line "17074" = Res#emem_res.pid,
+ ?line "2005-01-14 17:28:37.881980" = Res#emem_res.start_time,
+ ?line "2.0" = Res#emem_res.trace_version,
+ ?line "32 bits" = Res#emem_res.word_size,
+ ?line ["15",
+ "2665739", "8992", "548986", "16131", "539994",
+ "4334192", "1", "99", "15", "98",
+ "0", "0", "49", "0", "49"] = Res#emem_res.last_values,
+ ?line ["5972061", "9662",
+ "7987824", "5",
+ "2375680", "3"] = Res#emem_res.maximum,
+ ?line "0" = Res#emem_res.exit_code,
+ ?line emem_comment(Config).
+
+'pc_win2000_32b_emt2.0'(doc) -> [];
+'pc_win2000_32b_emt2.0'(suite) -> [];
+'pc_win2000_32b_emt2.0'(Config) when is_list(Config) ->
+ ?line Res = run_emem_on_casefile(Config),
+ ?line "test_server" = Res#emem_res.nodename,
+ ?line "E-788FCF5191B54" = Res#emem_res.hostname,
+ ?line "504" = Res#emem_res.pid,
+ ?line "2005-01-24 17:27:28.224000" = Res#emem_res.start_time,
+ ?line "2.0" = Res#emem_res.trace_version,
+ ?line "32 bits" = Res#emem_res.word_size,
+ ?line ["11",
+ "2932575", "8615", "641087", "68924", "632472"]
+ = Res#emem_res.last_values,
+ ?line ["5434206", "9285"] = Res#emem_res.maximum,
+ ?line "0" = Res#emem_res.exit_code,
+ ?line emem_comment(Config).
+
+'pc.smp_linux2.2.19pre17_32b_emt2.0'(doc) -> [];
+'pc.smp_linux2.2.19pre17_32b_emt2.0'(suite) -> [];
+'pc.smp_linux2.2.19pre17_32b_emt2.0'(Config) when is_list(Config) ->
+ ?line Res = run_emem_on_casefile(Config),
+ ?line "test_server" = Res#emem_res.nodename,
+ ?line "four-roses" = Res#emem_res.hostname,
+ ?line "20689" = Res#emem_res.pid,
+ ?line "2005-01-20 13:11:26.143077" = Res#emem_res.start_time,
+ ?line "2.0" = Res#emem_res.trace_version,
+ ?line "32 bits" = Res#emem_res.word_size,
+ ?line ["49",
+ "2901817", "9011", "521610", "10875", "512599",
+ "5392096", "2", "120", "10", "118",
+ "0", "0", "59", "0", "59"] = Res#emem_res.last_values,
+ ?line ["6182918", "9681",
+ "9062112", "6",
+ "2322432", "3"] = Res#emem_res.maximum,
+ ?line "0" = Res#emem_res.exit_code,
+ ?line emem_comment(Config).
+
+
+'powerpc_darwin7.7.0_32b_emt2.0'(doc) -> [];
+'powerpc_darwin7.7.0_32b_emt2.0'(suite) -> [];
+'powerpc_darwin7.7.0_32b_emt2.0'(Config) when is_list(Config) ->
+ ?line Res = run_emem_on_casefile(Config),
+ ?line "test_server" = Res#emem_res.nodename,
+ ?line "grima" = Res#emem_res.hostname,
+ ?line "13021" = Res#emem_res.pid,
+ ?line "2005-01-20 15:08:17.568668" = Res#emem_res.start_time,
+ ?line "2.0" = Res#emem_res.trace_version,
+ ?line "32 bits" = Res#emem_res.word_size,
+ ?line ["9",
+ "2784323", "8641", "531105", "15893", "522464"]
+ = Res#emem_res.last_values,
+ ?line ["6150376", "9311"] = Res#emem_res.maximum,
+ ?line "0" = Res#emem_res.exit_code,
+ ?line emem_comment(Config).
+
+'alpha_osf1v5.1_64b_emt2.0'(doc) -> [];
+'alpha_osf1v5.1_64b_emt2.0'(suite) -> [];
+'alpha_osf1v5.1_64b_emt2.0'(Config) when is_list(Config) ->
+ ?line Res = run_emem_on_casefile(Config),
+ ?line "test_server" = Res#emem_res.nodename,
+ ?line "thorin" = Res#emem_res.hostname,
+ ?line "224630" = Res#emem_res.pid,
+ ?line "2005-01-20 22:38:01.299632" = Res#emem_res.start_time,
+ ?line "2.0" = Res#emem_res.trace_version,
+ ?line "64 bits" = Res#emem_res.word_size,
+ ?line case Res#emem_res.max_word_size of
+ "32 bits" ->
+ ?line emem_comment(Config, ?EMEM_64_32_COMMENT);
+ "64 bits" ->
+ ?line ["22",
+ "6591992", "8625", "516785", "14805", "508160",
+ "11429184", "5", "127", "254", "122",
+ "0", "0", "61", "0", "61"] = Res#emem_res.last_values,
+ ?line ["7041775", "9295",
+ "11593024", "7",
+ "2097152", "3"] = Res#emem_res.maximum,
+ ?line "0" = Res#emem_res.exit_code,
+ ?line emem_comment(Config)
+ end.
+
+'sparc_sunos5.8_64b_emt2.0'(doc) -> [];
+'sparc_sunos5.8_64b_emt2.0'(suite) -> [];
+'sparc_sunos5.8_64b_emt2.0'(Config) when is_list(Config) ->
+ ?line Res = run_emem_on_casefile(Config),
+ ?line "test_server" = Res#emem_res.nodename,
+ ?line "gorbag" = Res#emem_res.hostname,
+ ?line "10907" = Res#emem_res.pid,
+ ?line "2005-01-20 13:48:34.677068" = Res#emem_res.start_time,
+ ?line "2.0" = Res#emem_res.trace_version,
+ ?line "64 bits" = Res#emem_res.word_size,
+ ?line case Res#emem_res.max_word_size of
+ "32 bits" ->
+ ?line emem_comment(Config, ?EMEM_64_32_COMMENT);
+ "64 bits" ->
+ ?line ["16",
+ "5032887", "8657", "530635", "14316", "521978",
+ "8627140", "5", "139", "19", "134",
+ "0", "0", "67", "0", "67"] = Res#emem_res.last_values,
+ ?line ["11695070", "9324",
+ "16360388", "10",
+ "4136960", "3"] = Res#emem_res.maximum,
+ ?line "0" = Res#emem_res.exit_code,
+ ?line emem_comment(Config)
+ end.
+
+'sparc_sunos5.8_32b_emt1.0'(doc) -> [];
+'sparc_sunos5.8_32b_emt1.0'(suite) -> [];
+'sparc_sunos5.8_32b_emt1.0'(Config) when is_list(Config) ->
+ ?line Res = run_emem_on_casefile(Config),
+ ?line "" = Res#emem_res.nodename,
+ ?line "" = Res#emem_res.hostname,
+ ?line "" = Res#emem_res.pid,
+ ?line "" = Res#emem_res.start_time,
+ ?line "1.0" = Res#emem_res.trace_version,
+ ?line "32 bits" = Res#emem_res.word_size,
+ ?line ["11",
+ "2558261", "8643", "560610", "15325", "551967"]
+ = Res#emem_res.last_values,
+ ?line ["2791121", "9317"] = Res#emem_res.maximum,
+ ?line "0" = Res#emem_res.exit_code,
+ ?line emem_comment(Config).
+
+'pc_win2000_32b_emt1.0'(doc) -> [];
+'pc_win2000_32b_emt1.0'(suite) -> [];
+'pc_win2000_32b_emt1.0'(Config) when is_list(Config) ->
+ ?line Res = run_emem_on_casefile(Config),
+ ?line "" = Res#emem_res.nodename,
+ ?line "" = Res#emem_res.hostname,
+ ?line "" = Res#emem_res.pid,
+ ?line "" = Res#emem_res.start_time,
+ ?line "1.0" = Res#emem_res.trace_version,
+ ?line "32 bits" = Res#emem_res.word_size,
+ ?line ["6",
+ "2965248", "8614", "640897", "68903", "632283"]
+ = Res#emem_res.last_values,
+ ?line ["3147090", "9283"] = Res#emem_res.maximum,
+ ?line "0" = Res#emem_res.exit_code,
+ ?line emem_comment(Config).
+
+
+'powerpc_darwin7.7.0_32b_emt1.0'(doc) -> [];
+'powerpc_darwin7.7.0_32b_emt1.0'(suite) -> [];
+'powerpc_darwin7.7.0_32b_emt1.0'(Config) when is_list(Config) ->
+ ?line Res = run_emem_on_casefile(Config),
+ ?line "" = Res#emem_res.nodename,
+ ?line "" = Res#emem_res.hostname,
+ ?line "" = Res#emem_res.pid,
+ ?line "" = Res#emem_res.start_time,
+ ?line "1.0" = Res#emem_res.trace_version,
+ ?line "32 bits" = Res#emem_res.word_size,
+ ?line ["8",
+ "2852991", "8608", "529662", "15875", "521054"]
+ = Res#emem_res.last_values,
+ ?line ["3173335", "9278"] = Res#emem_res.maximum,
+ ?line "0" = Res#emem_res.exit_code,
+ ?line emem_comment(Config).
+
+'alpha_osf1v5.1_64b_emt1.0'(doc) -> [];
+'alpha_osf1v5.1_64b_emt1.0'(suite) -> [];
+'alpha_osf1v5.1_64b_emt1.0'(Config) when is_list(Config) ->
+ ?line Res = run_emem_on_casefile(Config),
+ ?line "" = Res#emem_res.nodename,
+ ?line "" = Res#emem_res.hostname,
+ ?line "" = Res#emem_res.pid,
+ ?line "" = Res#emem_res.start_time,
+ ?line "1.0" = Res#emem_res.trace_version,
+ ?line "64 bits" = Res#emem_res.word_size,
+ ?line case Res#emem_res.max_word_size of
+ "32 bits" ->
+ ?line emem_comment(Config, ?EMEM_64_32_COMMENT);
+ "64 bits" ->
+ ?line ["22",
+ "6820094", "8612", "515518", "14812", "506906"]
+ = Res#emem_res.last_values,
+ ?line ["7292413", "9282"] = Res#emem_res.maximum,
+ ?line "0" = Res#emem_res.exit_code,
+ ?line emem_comment(Config)
+ end.
+
+'sparc_sunos5.8_64b_emt1.0'(doc) -> [];
+'sparc_sunos5.8_64b_emt1.0'(suite) -> [];
+'sparc_sunos5.8_64b_emt1.0'(Config) when is_list(Config) ->
+ ?line Res = run_emem_on_casefile(Config),
+ ?line "" = Res#emem_res.nodename,
+ ?line "" = Res#emem_res.hostname,
+ ?line "" = Res#emem_res.pid,
+ ?line "" = Res#emem_res.start_time,
+ ?line "1.0" = Res#emem_res.trace_version,
+ ?line "64 bits" = Res#emem_res.word_size,
+ ?line case Res#emem_res.max_word_size of
+ "32 bits" ->
+ ?line emem_comment(Config, ?EMEM_64_32_COMMENT);
+ "64 bits" ->
+ ?line ["15",
+ "4965746", "8234", "543940", "14443", "535706"]
+ = Res#emem_res.last_values,
+ ?line ["11697645", "8908"] = Res#emem_res.maximum,
+ ?line "0" = Res#emem_res.exit_code,
+ ?line emem_comment(Config)
+ end.
+
+%%
+%%
+%% Auxiliary functions
+%%
+%%
+
+receive_and_save_trace(PortNumber, FileName) when is_integer(PortNumber),
+ is_list(FileName) ->
+ {ok, F} = file:open(FileName, [write, compressed]),
+ {ok, LS} = gen_tcp:listen(PortNumber, [inet, {reuseaddr,true}, binary]),
+ {ok, S} = gen_tcp:accept(LS),
+ gen_tcp:close(LS),
+ receive_loop(S,F).
+
+receive_loop(Socket, File) ->
+ receive
+ {tcp, Socket, Data} ->
+ ok = file:write(File, Data),
+ receive_loop(Socket, File);
+ {tcp_closed, Socket} ->
+ file:close(File),
+ ok;
+ {tcp_error, Socket, Reason} ->
+ file:close(File),
+ {error, Reason}
+ end.
+
+send_trace({Host, PortNumber}, FileName) when is_list(Host),
+ is_integer(PortNumber),
+ is_list(FileName) ->
+ ?line {ok, F} = file:open(FileName, [read, compressed]),
+ ?line {ok, S} = gen_tcp:connect(Host, PortNumber, [inet,{packet, 0}]),
+ ?line send_loop(S, F);
+send_trace(EmuFlag, FileName) when is_list(EmuFlag),
+ is_list(FileName) ->
+ ?line ["+Mit", IpAddrStr, PortNoStr] = string:tokens(EmuFlag, " :"),
+ ?line send_trace({IpAddrStr, list_to_integer(PortNoStr)}, FileName).
+
+send_loop(Socket, File) ->
+ ?line case file:read(File, 128) of
+ {ok, Data} ->
+ ?line case gen_tcp:send(Socket, Data) of
+ ok -> ?line send_loop(Socket, File);
+ Error ->
+ ?line gen_tcp:close(Socket),
+ ?line file:close(File),
+ Error
+ end;
+ eof ->
+ ?line gen_tcp:close(Socket),
+ ?line file:close(File),
+ ?line ok;
+ Error ->
+ ?line gen_tcp:close(Socket),
+ ?line file:close(File),
+ ?line Error
+ end.
+
+check_emem(Dir, Type) when is_atom(Type) ->
+ ExeSuffix = case ?t:os_type() of
+ {win32, _} -> ".exe";
+ _ -> ""
+ end,
+ TypeSuffix = case Type of
+ opt -> "";
+ _ -> "." ++ atom_to_list(Type)
+ end,
+ Emem = "emem" ++ TypeSuffix ++ ExeSuffix,
+ case check_file(filename:join([Dir, Emem])) of
+ not_found -> ok;
+ File ->
+ Comment = case Type of
+ opt -> "";
+ _ -> "[emem " ++ atom_to_list(Type) ++ " compiled]"
+ end,
+ throw([{emem, File}, {emem_comment, Comment}])
+ end.
+
+check_dir(DirName) ->
+ case file:read_file_info(DirName) of
+ {ok, #file_info {type = directory, access = A}} when A == read;
+ A == read_write ->
+ DirName;
+ _ ->
+ not_found
+ end.
+
+check_file(FileName) ->
+ case file:read_file_info(FileName) of
+ {ok, #file_info {type = regular, access = A}} when A == read;
+ A == read_write ->
+ ?line FileName;
+ _ ->
+ ?line not_found
+ end.
+
+emem_comment(Config) when is_list(Config) ->
+ emem_comment(Config, "").
+
+emem_comment(Config, ExtraComment)
+ when is_list(Config), is_list(ExtraComment) ->
+ case {?config(emem_comment, Config), ExtraComment} of
+ {"", ""} -> ?line ok;
+ {"", XC} -> ?line {comment, XC};
+ {EmemC, ""} -> ?line {comment, EmemC};
+ {EmemC, XC} -> ?line {comment, EmemC ++ " " ++ XC}
+ end.
+
+run_emem_on_casefile(Config) ->
+ CaseName = atom_to_list(?config(testcase, Config)),
+ ?line File = filename:join([?config(data_dir, Config), CaseName ++ ".gz"]),
+ ?line case check_file(File) of
+ not_found ->
+ ?line ?t:fail({error, {filenotfound, File}});
+ _ ->
+ ?line ok
+ end,
+ ?line {ok, EmuFlag, Port} = start_emem(Config),
+ ?line Parent = self(),
+ ?line Ref = make_ref(),
+ ?line spawn_link(fun () ->
+ SRes = send_trace(EmuFlag, File),
+ Parent ! {Ref, SRes}
+ end),
+ ?line Res = get_emem_result(Port),
+ ?line receive
+ {Ref, ok} ->
+ ?line ok;
+ {Ref, SendError} ->
+ ?line ?t:format("Send result: ~p~n", [SendError])
+ end,
+ ?line Res.
+
+get_emem_result(Port) ->
+ ?line {Res, LV} = get_emem_result(Port, {#emem_res{}, []}),
+ ?line Res#emem_res{last_values = string:tokens(LV, " ")}.
+
+get_emem_result(Port, {_EmemRes, _LastValues} = Res) ->
+ ?line case get_emem_line(Port) of
+ eof ->
+ ?line Res;
+ Line ->
+ ?line get_emem_result(Port, parse_emem_line(Line, Res))
+ end.
+
+parse_emem_main_header_footer_line(Line, {ER, LV} = Res) ->
+
+ %% Header
+ ?line case has_prefix("> Nodename:", Line) of
+ {true, NN} ->
+ ?line throw({ER#emem_res{nodename = strip(NN)}, LV});
+ false -> ?line ok
+ end,
+ ?line case has_prefix("> Hostname:", Line) of
+ {true, HN} ->
+ ?line throw({ER#emem_res{hostname = strip(HN)}, LV});
+ false -> ?line ok
+ end,
+ ?line case has_prefix("> Pid:", Line) of
+ {true, P} ->
+ ?line throw({ER#emem_res{pid = strip(P)}, LV});
+ false -> ?line ok
+ end,
+ ?line case has_prefix("> Start time (UTC):", Line) of
+ {true, ST} ->
+ ?line throw({ER#emem_res{start_time = strip(ST)}, LV});
+ false -> ?line ok
+ end,
+ ?line case has_prefix("> Actual trace version:", Line) of
+ {true, TV} ->
+ ?line throw({ER#emem_res{trace_version = strip(TV)}, LV});
+ false -> ?line ok
+ end,
+ ?line case has_prefix("> Maximum trace word size:", Line) of
+ {true, MWS} ->
+ ?line throw({ER#emem_res{max_word_size = strip(MWS)}, LV});
+ false -> ?line ok
+ end,
+ ?line case has_prefix("> Actual trace word size:", Line) of
+ {true, WS} ->
+ ?line throw({ER#emem_res{word_size = strip(WS)}, LV});
+ false -> ?line ok
+ end,
+
+ %% Footer
+ ?line case has_prefix("> Maximum:", Line) of
+ {true, M} ->
+ ?line throw({ER#emem_res{maximum = string:tokens(M," ")}, LV});
+ false -> ?line ok
+ end,
+ ?line case has_prefix("> Emulator exited with code:", Line) of
+ {true, EC} ->
+ ?line throw({ER#emem_res{exit_code = strip(EC)}, LV});
+ false -> ?line ok
+ end,
+ ?line Res.
+
+parse_emem_header_line(_Line, {_ER, _LV} = Res) ->
+ ?line Res.
+
+parse_emem_value_line(Line, {EmemRes, _OldLastValues}) ->
+ ?line {EmemRes, Line}.
+
+parse_emem_line("", Res) ->
+ ?line Res;
+parse_emem_line(Line, Res) ->
+ ?line [Prefix | _] = Line,
+ case Prefix of
+ $> -> ?line catch parse_emem_main_header_footer_line(Line, Res);
+ $| -> ?line catch parse_emem_header_line(Line, Res);
+ _ -> ?line catch parse_emem_value_line(Line, Res)
+ end.
+
+start_emem(Config) when is_list(Config) ->
+ ?line Emem = ?config(emem, Config),
+ ?line Cd = case ignore_cores:dir(Config) of
+ false -> [];
+ Dir -> [{cd, Dir}]
+ end,
+ ?line case open_port({spawn, Emem ++ " -t -n -o -i 1"},
+ Cd ++ [{line, 1024}, eof]) of
+ Port when is_port(Port) -> ?line {ok, read_emu_flag(Port), Port};
+ Error -> ?line ?t:fail(Error)
+ end.
+
+read_emu_flag(Port) ->
+ ?line Line = case get_emem_line(Port) of
+ eof -> ?line ?t:fail(unexpected_end_of_file);
+ L -> ?line L
+ end,
+ ?line case has_prefix("> Emulator command line argument:", Line) of
+ {true, EmuFlag} -> EmuFlag;
+ false -> ?line read_emu_flag(Port)
+ end.
+
+get_emem_line(Port, Acc) ->
+ ?line receive
+ {Port, {data, {eol, Data}}} ->
+ ?line Res = case Acc of
+ [] -> ?line Data;
+ _ -> ?line lists:flatten([Acc|Data])
+ end,
+ ?line ?t:format("~s", [Res]),
+ ?line Res;
+ {Port, {data, {noeol, Data}}} ->
+ ?line get_emem_line(Port, [Acc|Data]);
+ {Port, eof} ->
+ ?line port_close(Port),
+ ?line eof
+ end.
+
+get_emem_line(Port) ->
+ ?line get_emem_line(Port, []).
+
+short_hostname([]) ->
+ [];
+short_hostname([$.|_]) ->
+ [];
+short_hostname([C|Cs]) ->
+ [C | short_hostname(Cs)].
+
+has_prefix([], List) when is_list(List) ->
+ {true, List};
+has_prefix([P|Xs], [P|Ys]) ->
+ has_prefix(Xs, Ys);
+has_prefix(_, _) ->
+ false.
+
+strip(Str) -> string:strip(Str).
+
+mk_nodename(Config) ->
+ {A, B, C} = now(),
+ atom_to_list(?MODULE)
+ ++ "-" ++ atom_to_list(?config(testcase, Config))
+ ++ "-" ++ integer_to_list(A*1000000000000 + B*1000000 + C).
+
+start_node(Name, Args) ->
+ ?line Pa = filename:dirname(code:which(?MODULE)),
+ ?line ?t:start_node(Name, peer, [{args, Args ++ " -pa " ++ Pa}]).
+
+% stop_node(Node) ->
+% ?t:stop_node(Node).
+
+is_debug_compiled() ->
+ is_debug_compiled(erlang:system_info(system_version)).
+
+is_debug_compiled([$d,$e,$b,$u,$g | _]) ->
+ true;
+is_debug_compiled([ _, _, _, _]) ->
+ false;
+is_debug_compiled([]) ->
+ false;
+is_debug_compiled([_|Rest]) ->
+ is_debug_compiled(Rest).
diff --git a/lib/tools/test/eprof_SUITE.erl b/lib/tools/test/eprof_SUITE.erl
new file mode 100644
index 0000000000..028fea8fe1
--- /dev/null
+++ b/lib/tools/test/eprof_SUITE.erl
@@ -0,0 +1,97 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2010. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(eprof_SUITE).
+
+-include("test_server.hrl").
+
+-export([all/1,tiny/1,eed/1]).
+
+all(suite) -> [tiny,eed].
+
+
+tiny(suite) -> [];
+tiny(Config) when is_list(Config) ->
+ ?line ensure_eprof_stopped(),
+ ?line {ok, OldCurDir} = file:get_cwd(),
+ Datadir = ?config(data_dir, Config),
+ Privdir = ?config(priv_dir, Config),
+ ?line TTrap=?t:timetrap(60*1000),
+ % (Trace)Compile to priv_dir and make sure the correct version is loaded.
+ ?line {ok,eprof_suite_test} = compile:file(filename:join(Datadir,
+ "eprof_suite_test"),
+ [trace,{outdir, Privdir}]),
+ ?line ok = file:set_cwd(Privdir),
+ ?line code:purge(eprof_suite_test),
+ ?line {module,eprof_suite_test} = code:load_file(eprof_suite_test),
+ ?line {ok,_Pid} = eprof:start(),
+ ?line nothing_to_analyse = eprof:analyse(),
+ ?line nothing_to_analyse = eprof:total_analyse(),
+ ?line eprof:profile([], eprof_suite_test, test, [Config]),
+ ?line ok = eprof:analyse(),
+ ?line ok = eprof:total_analyse(),
+ ?line ok = eprof:log("eprof_SUITE_logfile"),
+ ?line stopped = eprof:stop(),
+ ?line ?t:timetrap_cancel(TTrap),
+ ?line ok = file:set_cwd(OldCurDir),
+ ok.
+
+eed(suite) -> [];
+eed(Config) when is_list(Config) ->
+ ?line ensure_eprof_stopped(),
+ ?line Datadir = ?config(data_dir, Config),
+ ?line Privdir = ?config(priv_dir, Config),
+ ?line TTrap=?t:timetrap(5*60*1000),
+
+ %% (Trace)Compile to priv_dir and make sure the correct version is loaded.
+ ?line code:purge(eed),
+ ?line {ok,eed} = c:c(filename:join(Datadir, "eed"), [trace,{outdir,Privdir}]),
+ ?line {ok,_Pid} = eprof:start(),
+ ?line Script = filename:join(Datadir, "ed.script"),
+ ?line ok = file:set_cwd(Datadir),
+ ?line {T1,_} = statistics(runtime),
+ ?line ok = eed:file(Script),
+ ?line ok = eed:file(Script),
+ ?line ok = eed:file(Script),
+ ?line ok = eed:file(Script),
+ ?line ok = eed:file(Script),
+ ?line ok = eed:file(Script),
+ ?line ok = eed:file(Script),
+ ?line ok = eed:file(Script),
+ ?line ok = eed:file(Script),
+ ?line ok = eed:file(Script),
+ ?line {T2,_} = statistics(runtime),
+ ?line {ok,ok} = eprof:profile([], eed, file, [Script]),
+ ?line {T3,_} = statistics(runtime),
+ ?line profiling_already_stopped = eprof:stop_profiling(),
+ ?line ok = eprof:analyse(),
+ ?line ok = eprof:total_analyse(),
+ ?line ok = eprof:log("eprof_SUITE_logfile"),
+ ?line stopped = eprof:stop(),
+ ?line ?t:timetrap_cancel(TTrap),
+ S = lists:flatten(io_lib:format("~p times slower", [10*(T3-T2)/(T2-T1)])),
+ {comment,S}.
+
+ensure_eprof_stopped() ->
+ Pid = whereis(eprof),
+ case whereis(eprof) of
+ undefined ->
+ ok;
+ Pid ->
+ ?line stopped=eprof:stop()
+ end.
diff --git a/lib/tools/test/eprof_SUITE_data/ed.script b/lib/tools/test/eprof_SUITE_data/ed.script
new file mode 100644
index 0000000000..94531a9e98
--- /dev/null
+++ b/lib/tools/test/eprof_SUITE_data/ed.script
@@ -0,0 +1,8 @@
+H
+r eed.erl
+g/^[a-z][a-zA-Z_]*\(/i\
+%%% -------------------------------------------------------------\
+%%% A stupid function header.\
+%%% -------------------------------------------------------------
+1,$p
+q
diff --git a/lib/tools/test/eprof_SUITE_data/eed.erl b/lib/tools/test/eprof_SUITE_data/eed.erl
new file mode 100644
index 0000000000..0175abdd0e
--- /dev/null
+++ b/lib/tools/test/eprof_SUITE_data/eed.erl
@@ -0,0 +1,815 @@
+%%%----------------------------------------------------------------------
+%%% File : eed.erl
+%%% Author : Bjorn Gustavsson <bjorn@strider>
+%%% Purpose : Unix `ed' look-alike.
+%%% Created : 24 Aug 1997 by Bjorn Gustavsson <bjorn@strider>
+%%%----------------------------------------------------------------------
+
+-module(eed).
+-author('bjorn@strider').
+
+-export([edit/0, edit/1, file/1, cmd_line/1]).
+
+-record(state, {dot = 0, % Line number of dot.
+ upto_dot = [], % Lines up to dot (reversed).
+ after_dot = [], % Lines after dot.
+ lines = 0, % Total number of lines.
+ print=false, % Print after command.
+ filename=[], % Current file.
+ pattern, % Current pattern.
+ in_global=false, % True if executing global command.
+ input=[], % Global input stream.
+ undo, % Last undo state.
+ marks=[], % List of marks.
+ modified=false, % Buffer is modified.
+ opts=[{prompt, ''}], % Options.
+ last_error, % The last error encountered.
+ input_fd % Input file descriptor.
+ }).
+
+-record(line, {contents, % Contents of line.
+ mark=false % Marked (for global prefix).
+ }).
+
+cmd_line([Script]) ->
+ file(Script),
+ halt().
+
+file(Script) ->
+ case file:open(Script, [read]) of
+ {ok,Fd} ->
+ loop(#state{input_fd=Fd}),
+ ok;
+ {error,E} ->
+ {error,E}
+ end.
+
+edit() ->
+ loop(#state{input_fd=group_leader()}).
+
+edit(Name) ->
+ loop(command([$e|Name], #state{input_fd=group_leader()})).
+
+loop(St0) ->
+ {ok, St1, Cmd} = get_line(St0),
+ case catch command(lib:nonl(Cmd), St1) of
+ {'EXIT', Reason} ->
+ %% XXX Should clear outstanding global command here.
+ loop(print_error({'EXIT', Reason}, St1));
+ quit ->
+ ok;
+ {error, Reason} ->
+ loop(print_error(Reason, St1));
+ St2 when record(St2, state) ->
+ loop(St2)
+ end.
+
+command(Cmd, St) ->
+ case parse_command(Cmd, St) of
+ quit ->
+ quit;
+ St1 when function(St1#state.print) ->
+ if
+ St1#state.dot /= 0 ->
+ print_current(St1);
+ true ->
+ ok
+ end,
+ St1#state{print=false};
+ St1 when record(St1, state) ->
+ St1
+ end.
+
+get_line(St) ->
+ Opts = St#state.opts,
+ {value, {prompt, Prompt}} = lists:keysearch(prompt, 1, Opts),
+ get_line(Prompt, St).
+
+get_line(Prompt, St) when St#state.input == [] ->
+ Line = get_line1(St#state.input_fd, Prompt, []),
+ {ok, St, Line};
+get_line(_, St) ->
+ get_input(St#state.input, St, []).
+
+get_input([eof], St, []) ->
+ {ok, St, eof};
+get_input([eof], St, Result) ->
+ {ok, St#state{input=[eof]}, lists:reverse(Result)};
+get_input([$\n|Rest], St, Result) ->
+ {ok, St#state{input=Rest}, lists:reverse(Result)};
+get_input([C|Rest], St, Result) ->
+ get_input(Rest, St, [C|Result]).
+
+get_line1(Io, Prompt, Result) ->
+ get_line2(Io, io:get_line(Io, Prompt), Result).
+
+get_line2(Io, eof, []) ->
+ eof;
+get_line2(Io, eof, Result) ->
+ lists:reverse(Result);
+get_line2(Io, [$\\, $\n], Result) ->
+ get_line1(Io, '', [$\n|Result]);
+get_line2(Io, [$\n], Result) ->
+ lists:reverse(Result, [$\n]);
+get_line2(Io, [C|Rest], Result) ->
+ get_line2(Io, Rest, [C|Result]).
+
+print_error(Reason, St0) ->
+ St1 = St0#state{last_error=Reason},
+ io:put_chars("?\n"),
+ case lists:member(help_always, St1#state.opts) of
+ true ->
+ help_command([], [], St1),
+ St1;
+ false ->
+ St1
+ end.
+
+format_error(bad_command) -> "unknown command";
+format_error(bad_filename) -> "illegal or missing filename";
+format_error(bad_file) -> "cannot open input file";
+format_error(bad_linenum) -> "line out of range";
+format_error(bad_delimiter) -> "illegal or missing delimiter";
+format_error(bad_undo) -> "nothing to undo";
+format_error(bad_mark) -> "mark not lower case ascii";
+format_error(bad_pattern) -> "invalid regular expression";
+format_error(buffer_modified) -> "warning: expecting `w'";
+format_error(nested_globals) -> "multiple globals not allowed";
+format_error(nomatch) -> "search string not found";
+format_error(missing_space) -> "no space after command";
+format_error(garbage_after_command) -> "illegal suffix";
+format_error(not_implemented) -> "not implemented yet";
+format_error({'EXIT', {Code, {Mod, Func, Args}}}) ->
+ lists:flatten(io_lib:format("aborted due to bug (~p)",
+ [{Code, {Mod, Func, length(Args)}}]));
+format_error(A) -> atom_to_list(A).
+
+
+
+%%% Parsing commands.
+
+parse_command(Cmd, St) ->
+ parse_command(Cmd, St, []).
+
+parse_command(Cmd, State, Nums) ->
+ case get_one(Cmd, State) of
+ {ok, Num, Rest, NewState} ->
+ parse_next_address(Rest, NewState, [Num|Nums]);
+ false ->
+ parse_command1(Cmd, State, Nums)
+ end.
+
+parse_next_address([$,|Rest], State, Nums) ->
+ parse_command(Rest, State, Nums);
+parse_next_address([$;|Rest], State, [Num|Nums]) ->
+ parse_command(Rest, move_to(Num, State), [Num|Nums]);
+parse_next_address(Rest, State, Nums) ->
+ parse_command1(Rest, State, Nums).
+
+parse_command1([Letter|Rest], State, Nums) ->
+ Cont = fun(Fun, NumLines, Def) ->
+ execute_command(Fun, NumLines, Def, State, Nums, Rest) end,
+ parse_cmd_char(Letter, Cont);
+parse_command1([], State, Nums) ->
+ execute_command(fun print_command/3, 1, next, State, Nums, []).
+
+get_one(Cmd, St) ->
+ case get_address(Cmd, St) of
+ {ok, Addr, Cmd1, St1} ->
+ get_one1(Cmd1, Addr, St1);
+ false ->
+ get_one1(Cmd, false, St)
+ end.
+
+get_one1([D|Rest], false, St) when $0 =< D, D =< $9 ->
+ get_one2(get_number([D|Rest]), 1, 0, St);
+get_one1([D|Rest], Sum, St) when $0 =< D, D =< $9 ->
+ get_one2(get_number([D|Rest]), 1, Sum, St);
+get_one1([$+, D|Rest], Sum, St) when $0 =< D, D =< $9 ->
+ get_one2(get_number([D|Rest]), 1, Sum, St);
+get_one1([$-, D|Rest], Sum, St) when $0 =< D, D =< $9 ->
+ get_one2(get_number([D|Rest]), -1, Sum, St);
+get_one1([$+|Rest], Sum, St) ->
+ get_one2({ok, 1, Rest}, 1, Sum, St);
+get_one1([$-|Rest], Sum, St) ->
+ get_one2({ok, 1, Rest}, -1, Sum, St);
+get_one1(Cmd, false, St) ->
+ false;
+get_one1(Cmd, Sum, St) ->
+ {ok, Sum, Cmd, St}.
+
+get_one2({ok, Number, Rest}, Mul, false, St) ->
+ get_one1(Rest, St#state.dot+Mul*Number, St);
+get_one2({ok, Number, Rest}, Mul, Sum, St) ->
+ get_one1(Rest, Sum+Mul*Number, St).
+
+get_number(Cmd) ->
+ get_number(Cmd, 0).
+
+get_number([D|Rest], Result) when $0 =< D, D =< $9 ->
+ get_number(Rest, Result*10+D-$0);
+get_number(Rest, Result) ->
+ {ok, Result, Rest}.
+
+get_address([$.|Rest], State) ->
+ {ok, State#state.dot, Rest, State};
+get_address([$$|Rest], State) ->
+ {ok, State#state.lines, Rest, State};
+get_address([$', Mark|Rest], St) when $a =< Mark, Mark =< $z ->
+ case lists:keysearch(Mark, 2, St#state.marks) of
+ {value, {Line, Mark}} ->
+ {ok, Line, Rest, St};
+ false ->
+ {ok, 0, Rest, St}
+ end;
+get_address([$'|Rest], State) ->
+ error(bad_mark);
+get_address([$/|Rest], State) ->
+ scan_forward($/, Rest, State);
+get_address([$?|Rest], State) ->
+ error(not_implemented);
+get_address(Cmd, St) ->
+ false.
+
+scan_forward(End, Patt0, State) ->
+ {ok, Rest, NewState} = get_pattern(End, Patt0, State),
+ Dot = NewState#state.dot,
+ After = NewState#state.after_dot,
+ scan_forward1(Dot+1, After, NewState, Rest).
+
+scan_forward1(Linenum, [Line|Rest], State, RestCmd) ->
+ case regexp:first_match(Line#line.contents, State#state.pattern) of
+ {match, _, _} ->
+ {ok, Linenum, RestCmd, State};
+ nomatch ->
+ scan_forward1(Linenum+1, Rest, State, RestCmd)
+ end;
+scan_forward1(_, [], State, RestCmd) ->
+ Dot = State#state.dot,
+ Upto = State#state.upto_dot,
+ case scan_forward2(Dot, Upto, State, RestCmd) of
+ false ->
+ error(bad_linenum);
+ Other ->
+ Other
+ end.
+
+scan_forward2(0, [], State, RestCmd) ->
+ false;
+scan_forward2(Linenum, [Line|Rest], State, RestCmd) ->
+ case scan_forward2(Linenum-1, Rest, State, RestCmd) of
+ false ->
+ case regexp:first_match(Line#line.contents, State#state.pattern) of
+ {match, _, _} ->
+ {ok, Linenum, RestCmd, State};
+ nomatch ->
+ false
+ end;
+ Other ->
+ Other
+ end.
+
+parse_cmd_char($S, Cont) -> Cont(fun quest_command/3, 0, none);
+parse_cmd_char($T, Cont) -> Cont(fun time_command/3, 0, none);
+parse_cmd_char($=, Cont) -> Cont(fun print_linenum/3, 1, last);
+parse_cmd_char($a, Cont) -> Cont(fun append_command/3, 1, dot);
+parse_cmd_char($c, Cont) -> Cont(fun change_command/3, 2, dot);
+parse_cmd_char($d, Cont) -> Cont(fun delete_command/3, 2, dot);
+parse_cmd_char($e, Cont) -> Cont(fun enter_command/3, 0, none);
+parse_cmd_char($E, Cont) -> Cont(fun enter_always_command/3, 0, none);
+parse_cmd_char($f, Cont) -> Cont(fun file_command/3, 0, none);
+parse_cmd_char($g, Cont) -> Cont(fun global_command/3, 2, all);
+parse_cmd_char($h, Cont) -> Cont(fun help_command/3, 0, none);
+parse_cmd_char($H, Cont) -> Cont(fun help_always_command/3, 0, none);
+parse_cmd_char($i, Cont) -> Cont(fun insert_command/3, 1, dot);
+parse_cmd_char($k, Cont) -> Cont(fun mark_command/3, 1, dot);
+parse_cmd_char($l, Cont) -> Cont(fun list_command/3, 2, dot);
+parse_cmd_char($m, Cont) -> Cont(fun move_command/3, 2, dot);
+parse_cmd_char($n, Cont) -> Cont(fun number_command/3, 2, dot);
+parse_cmd_char($p, Cont) -> Cont(fun print_command/3, 2, dot);
+parse_cmd_char($P, Cont) -> Cont(fun prompt_command/3, 0, none);
+parse_cmd_char($q, Cont) -> Cont(fun quit_command/3, 0, none);
+parse_cmd_char($Q, Cont) -> Cont(fun quit_always_command/3, 0, none);
+parse_cmd_char($r, Cont) -> Cont(fun read_command/3, 1, last);
+parse_cmd_char($s, Cont) -> Cont(fun subst_command/3, 2, dot);
+parse_cmd_char($t, Cont) -> Cont(fun transpose_command/3, 2, dot);
+parse_cmd_char($u, Cont) -> Cont(fun undo_command/3, 0, none);
+parse_cmd_char($v, Cont) -> Cont(fun vglobal_command/3, 2, all);
+parse_cmd_char($w, Cont) -> Cont(fun write_command/3, 2, all);
+parse_cmd_char(_, Cont) -> error(bad_command).
+
+execute_command(Fun, NumLines, Def, State, Nums, Rest) ->
+ Lines = check_lines(NumLines, Def, Nums, State),
+ Fun(Rest, Lines, State).
+
+check_lines(0, _, [], _State) ->
+ [];
+check_lines(1, dot, [], #state{dot=Dot}) ->
+ [Dot];
+check_lines(1, next, [], State) when State#state.dot < State#state.lines ->
+ [State#state.dot+1];
+check_lines(1, last, [], State) ->
+ [State#state.lines];
+check_lines(1, _, [Num|_], State) when 0 =< Num, Num =< State#state.lines ->
+ [Num];
+check_lines(2, dot, [], #state{dot=Dot}) ->
+ [Dot, Dot];
+check_lines(2, all, [], #state{lines=Lines}) ->
+ [1, Lines];
+check_lines(2, _, [Num], State) when 0 =< Num, Num =< State#state.lines ->
+ [Num, Num];
+check_lines(2, _, [Num2, Num1|_], State)
+when 0 =< Num1, Num1 =< Num2, Num2 =< State#state.lines ->
+ [Num1, Num2];
+check_lines(_, _, _, _) ->
+ error(bad_linenum).
+
+
+%%% Executing commands.
+
+%% ($)= - print line number
+
+print_linenum(Rest, [Line], State) ->
+ NewState = check_trailing_p(Rest, State),
+ io:format("~w\n", [Line]),
+ NewState.
+
+%% ? - print state (for debugging)
+
+quest_command([], [], State) ->
+ io:format("~p\n", [State]),
+ State.
+
+%% Tcmd - time command
+
+time_command(Cmd, [], St) ->
+ Fun = fun parse_command/2,
+ erlang:garbage_collect(),
+ {Elapsed, Val} = timer:tc(erlang, apply, [Fun, [Cmd, St]]),
+ io:format("Time used: ~p s~n", [Elapsed/1000000.0]),
+ case Val of
+ {error, Reason} ->
+ throw({error, Reason});
+ Other ->
+ Other
+ end.
+
+%% (.)a - append text
+
+append_command(Rest, [Line], St0) ->
+ St1 = save_for_undo(St0),
+ append(move_to(Line, check_trailing_p(Rest, St1))).
+
+append(St0) ->
+ {ok, St1, Line0} = get_line('', St0),
+ case Line0 of
+ eof ->
+ St1;
+ ".\n" ->
+ St1;
+ Line ->
+ append(insert_line(Line, St1))
+ end.
+
+%% (.,.)c
+
+change_command(Rest, Lines, St0) ->
+ St1 = delete_command(Rest, Lines, St0),
+ St2 = append_command([], [St1#state.dot-1], St1),
+ save_for_undo(St2, St0).
+
+%% (.,.)d - delete lines
+
+delete_command(Rest, [0, Last], St) ->
+ error(bad_linenum);
+delete_command(Rest, [First, Last], St0) ->
+ St1 = check_trailing_p(Rest, save_for_undo(St0)),
+ delete(Last-First+1, move_to(Last, St1)).
+
+delete(0, St) when St#state.dot == St#state.lines ->
+ St;
+delete(0, St) ->
+ next_line(St);
+delete(Left, St0) ->
+ St1 = delete_current_line(St0),
+ delete(Left-1, St1).
+
+%% e file - replace buffer with new file
+
+enter_command(Name, [], St) when St#state.modified == true ->
+ error(buffer_modified);
+enter_command(Name, [], St0) ->
+ enter_always_command(Name, [], St0).
+
+%% E file - replace buffer with new file
+
+enter_always_command(Name, [], St0) ->
+ St1 = read_command(Name, [0], #state{filename=St0#state.filename,
+ opts=St0#state.opts}),
+ St1#state{modified=false}.
+
+%% f file - print filename; set filename
+
+file_command([], [], St) ->
+ io:format("~s~n", [St#state.filename]),
+ St;
+file_command([$_|Name0], [], St) ->
+ Name = skip_blanks(Name0),
+ file_command([], [], St#state{filename=Name});
+file_command(_, _, _) ->
+ error(missing_space).
+
+%% (1,$)g/RE/commands - execute commands on all matching lines.
+%% (1,$)v/RE/commands - execute commands on all non-matching lines.
+
+global_command(Cmd, Lines, St) ->
+ check_global0(true, Cmd, Lines, St).
+
+vglobal_command(Cmd, Lines, St) ->
+ check_global0(false, Cmd, Lines, St).
+
+check_global0(_, _, _, St) when St#state.in_global == true ->
+ error(nested_globals);
+check_global0(Sense, [Sep|Pattern], Lines, St0) ->
+ {ok, Cmd, St1} = get_pattern(Sep, Pattern, St0),
+ St2 = mark(Sense, Lines, St1),
+ do_global_command(Cmd, St2#state{in_global=true}, 0).
+
+mark(Sense, [First, Last], St0) ->
+ St1 = move_to(Last, St0),
+ mark1(Sense, First-1, St1).
+
+mark1(Sense, First, St) when St#state.dot == First ->
+ St;
+mark1(Sense, First, St) ->
+ [Line|Prev] = St#state.upto_dot,
+ NewLine = case match(St) of
+ true -> Line#line{mark=Sense};
+ false -> Line#line{mark=not(Sense)}
+ end,
+ mark1(Sense, First, prev_line(St#state{upto_dot=[NewLine|Prev]})).
+
+do_global_command(Cmd, St0, Matches) ->
+ case find_mark(St0) of
+ {ok, St1} ->
+ St2 = St1#state{input=Cmd++[eof]},
+ {ok, St3, Cmd1} = get_line(St2),
+ St4 = command(Cmd1, St3),
+ %% XXX There might be several commands.
+ do_global_command(Cmd, St4, Matches+1);
+ false when Matches == 0 ->
+ error(nomatch);
+ false ->
+ St0#state{in_global=false, input=[]}
+ end.
+
+find_mark(State) ->
+ find_mark(State#state.lines, State).
+
+find_mark(0, _State) ->
+ false;
+find_mark(Limit, State) when State#state.dot == 0 ->
+ find_mark(Limit, next_line(State));
+find_mark(Limit, State) ->
+ case State#state.upto_dot of
+ [Line|Prev] when Line#line.mark == true ->
+ NewLine = Line#line{mark=false},
+ {ok, State#state{upto_dot=[NewLine|Prev]}};
+ _Other ->
+ find_mark(Limit-1, wrap_next_line(State))
+ end.
+
+%% h - print info about last error
+
+help_command([], [], St) ->
+ case St#state.last_error of
+ undefined ->
+ St;
+ Reason ->
+ io:put_chars(format_error(Reason)),
+ io:nl(),
+ St
+ end;
+help_command(_, _, _) ->
+ error(garbage_after_command).
+
+%% H - toggle automatic help mode on/off
+
+help_always_command([], [], St) ->
+ Opts = St#state.opts,
+ case lists:member(help_always, Opts) of
+ true ->
+ St#state{opts=Opts--[help_always]};
+ false ->
+ help_command([], [], St),
+ St#state{opts=[help_always|Opts]}
+ end.
+
+%% (.)i - insert text
+
+insert_command(Rest, [0], State) ->
+ error(bad_linenum);
+insert_command(Rest, [Line], State) ->
+ append_command(Rest, [Line-1], State).
+
+%% (.)kx - mark line
+
+mark_command(_, [0], St) ->
+ error(bad_linenum);
+mark_command([Mark|Rest], [Line], St) when $a =< Mark, Mark =< $z ->
+ error(not_implemented);
+mark_command(_, _, _) ->
+ error(bad_mark).
+
+%% (.,.)l - list lines
+
+list_command(Rest, Lines, St) ->
+ print([$l|Rest], Lines, St).
+
+%% (.,.)m - move lines
+
+move_command(Cmd, [First, Last], St) ->
+ error(not_implemented).
+
+%% (.,.)t - copy lines
+
+transpose_command(Cmd, [First, Last], St) ->
+ error(not_implemented).
+
+%% (.,.)n - print lines with line numbers
+
+number_command(Rest, Lines, St) ->
+ print([$n|Rest], Lines, St).
+
+%% (.,.)p - print lines
+
+print_command(Rest, Lines, St) ->
+ print([$p|Rest], Lines, St).
+
+%% P - toggle prompt
+
+prompt_command([], [], St) ->
+ Opts = St#state.opts,
+ case lists:keysearch(prompt, 1, Opts) of
+ {value, {prompt, ''}} ->
+ St#state{opts=[{prompt, '*'}|Opts]};
+ {value, Value} ->
+ St#state{opts=[{prompt, ''} | Opts--[Value]]}
+ end;
+prompt_command(_, _, _) ->
+ error(garbage_after_command).
+
+%% q - quit editor
+
+quit_command([], [], _) ->
+ quit;
+quit_command(_, _, _) ->
+ error(garbage_after_command).
+
+%% Q - quit editor
+
+quit_always_command([], [], _) ->
+ quit;
+quit_always_command(_, _, _) ->
+ error(garbage_after_command).
+
+%% ($)r file - read file
+
+read_command([], _, St) when St#state.filename == [] ->
+ error(bad_filename);
+read_command([], [After], St) ->
+ read(After, St#state.filename, St);
+read_command([$ |Name0], [After], St) when St#state.filename == [] ->
+ Name = skip_blanks(Name0),
+ read(After, Name, St#state{filename=Name});
+read_command([$ |Name0], [After], St) ->
+ Name = skip_blanks(Name0),
+ read(After, Name, St);
+read_command(_, _, _) ->
+ error(missing_space).
+
+read(After, Name, St0) ->
+ case file:read_file(Name) of
+ {ok, Bin} ->
+ Chars = size(Bin),
+ St1 = move_to(After, St0),
+ St2 = insert_line(binary_to_list(Bin), St1),
+ io:format("~w~n", [Chars]),
+ St2;
+ {error, _} ->
+ error(bad_file)
+ end.
+
+%% s/pattern/replacement/gp
+
+subst_command(_, [0, _], _) ->
+ error(bad_linenum);
+subst_command([$ |Cmd0], [First, Last], St0) ->
+ error(bad_delimiter);
+subst_command([$\n|Cmd0], [First, Last], St0) ->
+ error(bad_delimiter);
+subst_command([Sep|Cmd0], [First, Last], St0) ->
+ St1 = save_for_undo(St0),
+ {ok, Cmd1, St2} = get_pattern(Sep, Cmd0, St1),
+ {ok, Replacement, Cmd2} = get_replacement(Sep, Cmd1),
+ {ok, Sub, Cmd3} = subst_check_gflag(Cmd2),
+ St3 = check_trailing_p(Cmd3, St2),
+ subst_command(Last-First+1, Sub, Replacement, move_to(First-1, St3), nomatch);
+subst_command([], _, _) ->
+ error(bad_delimiter).
+
+subst_command(0, _, _, _, nomatch) ->
+ error(nomatch);
+subst_command(0, _, _, _, StLast) when record(StLast, state) ->
+ StLast;
+subst_command(Left, Sub, Repl, St0, LastMatch) ->
+ St1 = next_line(St0),
+ [Line|_] = St1#state.upto_dot,
+ case regexp:Sub(Line#line.contents, St1#state.pattern, Repl) of
+ {ok, _, 0} ->
+ subst_command(Left-1, Sub, Repl, St1, LastMatch);
+ {ok, NewContents, _} ->
+ %% XXX This doesn't work with marks.
+ St2 = delete_current_line(St1),
+ St3 = insert_line(NewContents, St2),
+ subst_command(Left-1, Sub, Repl, St3, St3)
+ end.
+
+subst_check_gflag([$g|Cmd]) -> {ok, gsub, Cmd};
+subst_check_gflag(Cmd) -> {ok, sub, Cmd}.
+
+%% u - undo
+
+undo_command([], [], St) when St#state.undo == undefined ->
+ error(bad_undo);
+undo_command([], [], #state{undo=Undo}) ->
+ Undo;
+undo_command(_, _, _) ->
+ error(garbage_after_command).
+
+%% (1,$)w - write buffer to file
+
+write_command(Cmd, [First, Last], St) ->
+ error(not_implemented).
+
+
+%%% Primitive buffer operations.
+
+print_current(St) ->
+ [Line|_] = St#state.upto_dot,
+ Printer = St#state.print,
+ Printer(Line#line.contents, St).
+
+delete_current_line(St) when St#state.dot == 0 ->
+ error(bad_linenum);
+delete_current_line(St) ->
+ Lines = St#state.lines,
+ [_|Prev] = St#state.upto_dot,
+ St#state{dot=St#state.dot-1, upto_dot=Prev, lines=Lines-1, modified=true}.
+
+insert_line(Line, State) ->
+ insert_line1(Line, State, []).
+
+insert_line1([$\n|Rest], State, Result) ->
+ NewState = insert_single_line(lists:reverse(Result, [$\n]), State),
+ insert_line1(Rest, NewState, []);
+insert_line1([C|Rest], State, Result) ->
+ insert_line1(Rest, State, [C|Result]);
+insert_line1([], State, []) ->
+ State;
+insert_line1([], State, Result) ->
+ insert_single_line(lists:reverse(Result, [$\n]), State).
+
+insert_single_line(Line0, State) ->
+ Line = #line{contents=Line0},
+ Dot = State#state.dot,
+ Before = State#state.upto_dot,
+ Lines = State#state.lines,
+ %% XXX Avoid updating the record every time.
+ State#state{dot=Dot+1, upto_dot=[Line|Before], lines=Lines+1, modified=true}.
+
+move_to(Line, State) when Line < State#state.dot ->
+ move_to(Line, prev_line(State));
+move_to(Line, State) when State#state.dot < Line ->
+ move_to(Line, next_line(State));
+move_to(Line, State) when Line == State#state.dot ->
+ State.
+
+prev_line(State) ->
+ Dot = State#state.dot,
+ Before = State#state.upto_dot,
+ After = State#state.after_dot,
+ State#state{dot=Dot-1, upto_dot=tl(Before), after_dot=[hd(Before)|After]}.
+
+next_line(State) ->
+ Dot = State#state.dot,
+ Before = State#state.upto_dot,
+ After = State#state.after_dot,
+ State#state{dot=Dot+1, upto_dot=[hd(After)|Before], after_dot=tl(After)}.
+
+wrap_next_line(State) when State#state.dot == State#state.lines ->
+ move_to(1, State);
+wrap_next_line(State) ->
+ next_line(State).
+
+
+%%% Utilities.
+
+get_pattern(End, Cmd, State) ->
+ get_pattern(End, Cmd, State, []).
+
+get_pattern(End, [End|Rest], State, []) when State#state.pattern /= undefined ->
+ {ok, Rest, State};
+get_pattern(End, [End|Rest], State, Result) ->
+ case regexp:parse(lists:reverse(Result)) of
+ {error, _} ->
+ error(bad_pattern);
+ {ok, Re} ->
+ {ok, Rest, State#state{pattern=Re}}
+ end;
+get_pattern(End, [C|Rest], State, Result) ->
+ get_pattern(End, Rest, State, [C|Result]);
+get_pattern(End, [], State, Result) ->
+ get_pattern(End, [End], State, Result).
+
+get_replacement(End, Cmd) ->
+ get_replacement(End, Cmd, []).
+
+get_replacement(End, [End|Rest], Result) ->
+ {ok, lists:reverse(Result), Rest};
+get_replacement(End, [$\\, $&|Rest], Result) ->
+ get_replacement(End, Rest, [$&, $\\|Result]);
+get_replacement(End, [$\\, C|Rest], Result) ->
+ get_replacement(End, Rest, [C|Result]);
+get_replacement(End, [C|Rest], Result) ->
+ get_replacement(End, Rest, [C|Result]);
+get_replacement(End, [], Result) ->
+ get_replacement(End, [End], Result).
+
+check_trailing_p([$l], St) ->
+ St#state{print=fun(Line, _) -> lister(Line, 0) end};
+check_trailing_p([$n], St) ->
+ St#state{print=fun numberer/2};
+check_trailing_p([$p], St) ->
+ St#state{print=fun(Line, _) -> io:put_chars(Line) end};
+check_trailing_p([], State) ->
+ State;
+check_trailing_p(Other, State) ->
+ error(garbage_after_command).
+
+error(Reason) ->
+ throw({error, Reason}).
+
+match(State) when State#state.dot == 0 ->
+ false;
+match(State) ->
+ [Line|_] = State#state.upto_dot,
+ Re = State#state.pattern,
+ case regexp:first_match(Line#line.contents, Re) of
+ {match, _, _} -> true;
+ nomatch -> false
+ end.
+
+skip_blanks([$ |Rest]) ->
+ skip_blanks(Rest);
+skip_blanks(Rest) ->
+ Rest.
+
+print(Rest, [Line], St0) when Line > 0 ->
+ St1 = check_trailing_p(Rest, St0),
+ print(Line, move_to(Line-1, St1));
+print(Rest, [First, Last], St0) when First > 0 ->
+ St1 = check_trailing_p(Rest, St0),
+ print(Last, move_to(First-1, St1)).
+
+print(Last, St) when St#state.dot == Last ->
+ St#state{print=false};
+print(Last, St0) ->
+ St1 = next_line(St0),
+ print_current(St1),
+ print(Last, St1).
+
+lister(Rest, 64) ->
+ io:put_chars("\\\n"),
+ lister(Rest, 0);
+lister([C|Rest], Num) ->
+ list_char(C),
+ lister(Rest, Num+1);
+lister([], _) ->
+ ok.
+
+list_char($\t) ->
+ io:put_chars("\\t");
+list_char($\n) ->
+ io:put_chars("$\n");
+list_char(C) ->
+ io:put_chars([C]).
+
+numberer(Line, St) ->
+ io:format("~w\t~s", [St#state.dot, Line]).
+
+save_for_undo(St) ->
+ St#state{undo=St#state{undo=undefined, print=false}}.
+
+save_for_undo(St, OldSt) ->
+ St#state{undo=OldSt#state{undo=undefined, print=false}}.
diff --git a/lib/tools/test/eprof_SUITE_data/eprof_suite_test.erl b/lib/tools/test/eprof_SUITE_data/eprof_suite_test.erl
new file mode 100644
index 0000000000..a88b6e21f2
--- /dev/null
+++ b/lib/tools/test/eprof_SUITE_data/eprof_suite_test.erl
@@ -0,0 +1,74 @@
+%% ``The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved via the world wide web at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
+%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
+%% AB. All Rights Reserved.''
+%%
+%% $Id$
+%%
+%%%----------------------------------------------------------------------
+%%% Purpose : A priority queue.
+%%%----------------------------------------------------------------------
+%%% This module implements a priority queue as defined in
+%%% "Priority Queues and the STL" by Mark Nelson in Dr.Dobb's Journal, Jan 1996
+%%% see http://web2.airmail.net/markn/articles/pq_stl/priority.htm for more
+%%% information. (A heap implementation is planned aswell)
+%%%----------------------------------------------------------------------
+%%% The items of the queue is kept priority sorted, and because of that,
+%%% a push() operation costs more than a pop() operation (wich only
+%%% needs to return the top item of the queue(read: list)).
+%%%----------------------------------------------------------------------
+%%% The priority queue can be deceptively nice to use when creating for
+%%% example a Huffman coding tree.
+%%% See http://web2.airmail.net/markn/articles/pq_stl/priority.htm or
+%%% Dr.Dobb's Journal Jan, 96 for more information on this.
+%%%----------------------------------------------------------------------
+
+-module(eprof_suite_test).
+-export([test/1]).
+-export([new/0, push/3, pop/1]).
+
+test(Config) ->
+ Q1=new(),
+ Q2=push(Q1, "monkey", 3),
+ Q3=push(Q2, "banana", 4),
+ Q4=push(Q3, "jungle", 2),
+ Q5=push(Q4, "world", 5),
+ Q6=push(Q5, "universe",6),
+ Q7=push(Q6, "peanut", 1),
+% io:format("~p~n",[Q7]),
+ {Itm, Q8}=pop(Q7),
+ ok.
+
+%% Returns a new priority queue.
+new() ->
+ [].
+
+%% Pushes a new item with a set priority into the queue.
+push(Queue, Itm, Pri) ->
+ insert(Queue, Itm, Pri, []).
+
+%% Pops the item with the highest priority out of the queue.
+pop([{Itm, Pri}|Queue]) ->
+ {Itm, Queue}.
+
+%% --- -- -
+%% Support functions.
+insert([], Itm, Pri, NewQ) ->
+ lists:flatten([lists:reverse(NewQ)|[{Itm, Pri}]]);
+% Itm>QItm>NewQ>Queue
+insert([{QItm,QPri}|Queue], Itm, Pri, NewQ) when Pri>QPri->
+ A = [{Itm, Pri}|[{QItm, QPri}]],
+ lists:flatten([[A|NewQ]|Queue]);
+insert([QItm|Rest], Itm, Pri, NewQ) ->
+ insert(Rest, Itm, Pri, [QItm|NewQ]).
+%% --- -- -
diff --git a/lib/tools/test/fprof_SUITE.erl b/lib/tools/test/fprof_SUITE.erl
new file mode 100644
index 0000000000..e437007e76
--- /dev/null
+++ b/lib/tools/test/fprof_SUITE.erl
@@ -0,0 +1,1191 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2001-2010. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(fprof_SUITE).
+
+-include("test_server.hrl").
+
+%% Test server framework exports
+-export([all/1, not_run/1]).
+
+%% Test suites
+-export([stack_seq/1, tail_seq/1, create_file_slow/1, spawn_simple/1,
+ imm_tail_seq/1, imm_create_file_slow/1, imm_compile/1,
+ cpu_create_file_slow/1]).
+
+%% Other exports
+-export([create_file_slow/2]).
+
+
+%% Debug exports
+-export([parse/1, verify/2]).
+-export([spawn_simple_test/3]).
+
+
+-define(line_trace,true).
+
+%-define(debug,true).
+-ifdef(debug).
+-define(dbg(Str,Args), io:format(Str,Args)).
+-else.
+-define(dbg(Str,Args), ok).
+-endif.
+
+
+
+%%%---------------------------------------------------------------------
+%%% Test suites
+%%%---------------------------------------------------------------------
+
+
+
+all(doc) ->
+ ["Test the 'fprof' profiling tool."];
+all(suite) ->
+ case test_server:is_native(?MODULE) of
+ true ->
+ [not_run];
+ false ->
+ [stack_seq, tail_seq, create_file_slow, spawn_simple,
+ imm_tail_seq, imm_create_file_slow, imm_compile,
+ cpu_create_file_slow]
+ end.
+
+not_run(Config) when is_list(Config) ->
+ {skipped, "Native code"}.
+
+%%%---------------------------------------------------------------------
+
+stack_seq(doc) ->
+ ["Tests a stack recursive variant of lists:seq/3"];
+stack_seq(suite) ->
+ [];
+stack_seq(Config) when is_list(Config) ->
+ ?line Timetrap = ?t:timetrap(?t:seconds(20)),
+ ?line PrivDir = ?config(priv_dir, Config),
+ ?line TraceFile =
+ filename:join(PrivDir, ?MODULE_STRING"_stack_seq.trace"),
+ ?line AnalysisFile =
+ filename:join(PrivDir, ?MODULE_STRING"_stack_seq.analysis"),
+ ?line Start = 1,
+ ?line Stop = 1000,
+ ?line Succ = fun (X) -> X + 1 end,
+ ?line ok = fprof:stop(kill),
+ %%
+ ?line TS0 = erlang:now(),
+ ?line R0 = fprof:apply(fun seq/3, [Start, Stop, Succ], [{file, TraceFile}]),
+ ?line TS1 = erlang:now(),
+ ?line R = seq(Start, Stop, Succ),
+ ?line TS2 = erlang:now(),
+ ?line ok = fprof:profile(file, TraceFile),
+ ?line ok = fprof:analyse(),
+ ?line ok = fprof:analyse(dest, AnalysisFile),
+ ?line ok = fprof:stop(),
+ ?line R = R0,
+ %%
+ ?line {ok, [T, P]} = parse(AnalysisFile),
+ ?line io:format("~p~n~n~p~n", [P, ets:tab2list(T)]),
+ ?line ok = (catch verify(T, P)),
+ ?line Proc = pid_to_list(self()),
+ ?line case P of
+ [{analysis_options, _},
+ [{totals, _, Acc, _}],
+ [{Proc, _, undefined, _} | _]] ->
+ ok
+ end,
+ %%
+ ?line check_own_and_acc(TraceFile,AnalysisFile),
+ %%
+ ?line ets:delete(T),
+ ?line file:delete(TraceFile),
+ ?line file:delete(AnalysisFile),
+ ?line ?t:timetrap_cancel(Timetrap),
+ ?line Acc1 = ts_sub(TS1, TS0),
+ ?line Acc2 = ts_sub(TS2, TS1),
+ ?line io:format("ts:~w, fprof:~w, bare:~w.~n", [Acc, Acc1, Acc2]),
+ {comment, io_lib:format("~p times slower", [Acc1/Acc2])}.
+
+%%%---------------------------------------------------------------------
+
+tail_seq(doc) ->
+ ["Tests a tail recursive variant of lists:seq/3"];
+tail_seq(suite) ->
+ [];
+tail_seq(Config) when is_list(Config) ->
+ ?line Timetrap = ?t:timetrap(?t:seconds(10)),
+ ?line PrivDir = ?config(priv_dir, Config),
+ ?line TraceFile =
+ filename:join(PrivDir, ?MODULE_STRING"_tail_seq.trace"),
+ ?line AnalysisFile =
+ filename:join(PrivDir, ?MODULE_STRING"_tail_seq.analysis"),
+ ?line Start = 1,
+ ?line Stop = 1000,
+ ?line Succ = fun (X) -> X + 1 end,
+ ?line ok = fprof:stop(kill),
+ %%
+ ?line TS0 = erlang:now(),
+ ?line R = seq_r(Start, Stop, Succ),
+ ?line TS1 = erlang:now(),
+ %%
+ ?line R1 = fprof:apply(fun seq_r/3, [Start, Stop, Succ],
+ [{file, TraceFile}]),
+ ?line TS2 = erlang:now(),
+ ?line ok = fprof:profile([{file,TraceFile}]),
+ ?line ok = fprof:analyse(),
+ ?line ok = fprof:analyse(dest, AnalysisFile),
+ ?line ok = fprof:stop(),
+ ?line R = R1,
+ %%
+ ?line {ok, [T, P]} = parse(AnalysisFile),
+ ?line io:format("~p~n~n~p~n", [P, ets:tab2list(T)]),
+ ?line ok = verify(T, P),
+ ?line Proc = pid_to_list(self()),
+ ?line case P of
+ [{analysis_options, _},
+ [{totals, _, Acc, _}],
+ [{Proc, _, undefined, _} | _]] ->
+ ok
+ end,
+ %%
+ ?line check_own_and_acc(TraceFile,AnalysisFile),
+ %%
+ ?line ets:delete(T),
+ ?line file:delete(TraceFile),
+ ?line file:delete(AnalysisFile),
+ ?line ?t:timetrap_cancel(Timetrap),
+ ?line Acc1 = ts_sub(TS1, TS0),
+ ?line Acc2 = ts_sub(TS2, TS1),
+ ?line io:format("ts:~w, fprof:~w, bare:~w.~n", [Acc, Acc2, Acc1]),
+ {comment, io_lib:format("~p times slower", [Acc2/Acc1])}.
+
+%%%---------------------------------------------------------------------
+
+create_file_slow(doc) ->
+ ["Tests the create_file_slow benchmark"];
+create_file_slow(suite) ->
+ [];
+create_file_slow(Config) when is_list(Config) ->
+ ?line Timetrap = ?t:timetrap(?t:seconds(40)),
+ ?line PrivDir = ?config(priv_dir, Config),
+ ?line TraceFile =
+ filename:join(PrivDir, ?MODULE_STRING"_create_file_slow.trace"),
+ ?line AnalysisFile =
+ filename:join(PrivDir, ?MODULE_STRING"_create_file_slow.analysis"),
+ ?line DataFile =
+ filename:join(PrivDir, ?MODULE_STRING"_create_file_slow.data"),
+ ?line ok = fprof:stop(kill),
+ %%
+ ?line TS0 = erlang:now(),
+ ?line ok = create_file_slow(DataFile, 1024),
+ ?line TS1 = erlang:now(),
+ %%
+ ?line ok = file:delete(DataFile),
+ ?line TS2 = erlang:now(),
+ ?line ok = fprof:apply(?MODULE, create_file_slow, [DataFile, 1024],
+ [{file, TraceFile}]),
+ ?line TS3 = erlang:now(),
+ ?line ok = fprof:profile(file, TraceFile),
+ ?line ok = fprof:analyse(),
+ ?line ok = fprof:analyse(dest, AnalysisFile),
+ ?line ok = fprof:stop(),
+ %%
+ ?line {ok, [T, P]} = parse(AnalysisFile),
+ ?line io:format("~p~n~n~p~n", [P, ets:tab2list(T)]),
+ ?line ok = verify(T, P),
+ ?line Proc = pid_to_list(self()),
+ ?line case P of
+ [{analysis_options, _},
+ [{totals, _, Acc, _}],
+ [{Proc, _, undefined, _} | _]] ->
+ ok
+ end,
+ %%
+ ?line check_own_and_acc(TraceFile,AnalysisFile),
+ %%
+ ?line ets:delete(T),
+ ?line file:delete(DataFile),
+ ?line file:delete(TraceFile),
+ ?line file:delete(AnalysisFile),
+ ?line ?t:timetrap_cancel(Timetrap),
+ ?line Acc1 = ts_sub(TS1, TS0),
+ ?line Acc3 = ts_sub(TS3, TS2),
+ ?line io:format("ts:~w, fprof:~w, bare:~w.~n", [Acc, Acc3, Acc1]),
+ {comment, io_lib:format("~p times slower", [Acc3/Acc1])}.
+
+
+
+%%%---------------------------------------------------------------------
+
+spawn_simple(doc) ->
+ ["Tests process spawn"];
+spawn_simple(suite) ->
+ [];
+spawn_simple(Config) when is_list(Config) ->
+ ?line Timetrap = ?t:timetrap(?t:seconds(30)),
+ ?line PrivDir = ?config(priv_dir, Config),
+ ?line TraceFile =
+ filename:join(PrivDir, ?MODULE_STRING"_spawn_simple.trace"),
+ ?line AnalysisFile =
+ filename:join(PrivDir, ?MODULE_STRING"_spawn_simple.analysis"),
+ ?line Start = 1,
+ ?line Stop = 1000,
+ ?line Succ = fun (X) -> X + 1 end,
+ ?line ok = fprof:stop(kill),
+ %%
+ ?line TS0 = erlang:now(),
+ ?line {{_, R1}, {_, R2}} = spawn_simple_test(Start, Stop, Succ),
+ ?line TS1 = erlang:now(),
+ %%
+ ?line ok = fprof:trace(start, TraceFile),
+ ?line {{P1, R3}, {P2, R4}} = spawn_simple_test(Start, Stop, Succ),
+ ?line ok = fprof:trace(stop),
+ ?line TS2 = erlang:now(),
+ ?line ok = fprof:profile(file, TraceFile),
+ ?line ok = fprof:analyse(),
+ ?line ok = fprof:analyse(dest, AnalysisFile),
+ ?line ok = fprof:stop(),
+ ?line R1 = R3,
+ ?line R2 = R4,
+ %%
+ ?line {ok, [T, P]} = parse(AnalysisFile),
+ ?line io:format("~p~n~n~p~n", [P, ets:tab2list(T)]),
+ ?line ok = verify(T, P),
+ ?line Proc1 = pid_to_list(P1),
+ ?line Proc2 = pid_to_list(P2),
+ ?line Proc0 = pid_to_list(self()),
+ ?line io:format("~p~n ~p ~p ~p~n", [P, Proc0, Proc1, Proc2]),
+ ?line [{analysis_options, _}, [{totals, _, Acc, _}] | Procs] = P,
+ ?line [[{Proc0, _, undefined, _} | _]] =
+ lists:filter(fun ([Pt | _]) when element(1, Pt) == Proc0 -> true;
+ (_) -> false
+ end, Procs),
+ ?line [[{Proc1, _, undefined, _},
+ {spawned_by, Proc0},
+ {spawned_as, {erlang, apply, ["#Fun"++_, []]}},
+ {initial_calls, [{erlang, apply, 2},
+ {?MODULE, '-spawn_simple_test/3-fun-0-', 4}]}
+ | _]] =
+ lists:filter(fun ([Pt | _]) when element(1, Pt) == Proc1 -> true;
+ (_) -> false
+ end, Procs),
+ ?line [[{Proc2, _, undefined, _},
+ {spawned_by, Proc0},
+ {spawned_as, {erlang, apply, ["#Fun"++_, []]}},
+ {initial_calls, [{erlang, apply, 2},
+ {?MODULE, '-spawn_simple_test/3-fun-1-', 4}]}
+ | _]] =
+ lists:filter(fun ([Pt | _]) when element(1, Pt) == Proc2 -> true;
+ (_) -> false
+ end, Procs),
+ ?line 3 = length(Procs),
+ ?line R1 = lists:reverse(R2),
+ %%
+ ?line check_own_and_acc(TraceFile,AnalysisFile),
+ %%
+ ?line ets:delete(T),
+ ?line file:delete(TraceFile),
+ ?line file:delete(AnalysisFile),
+ ?line ?t:timetrap_cancel(Timetrap),
+ ?line Acc1 = ts_sub(TS1, TS0),
+ ?line Acc2 = ts_sub(TS2, TS1),
+ ?line io:format("ts:~w, fprof:~w, bare:~w.~n", [Acc, Acc2, Acc1]),
+ {comment, io_lib:format("~p times slower", [Acc2/Acc1])}.
+
+
+spawn_simple_test(Start, Stop, Succ) ->
+ Parent = self(),
+ Seq =
+ spawn_link(
+ fun () ->
+ Parent ! {self(), seq(Start, Stop, Succ)}
+ end),
+ SeqR =
+ spawn_link(
+ fun () ->
+ Parent ! {self(), seq_r(Start, Stop, Succ)}
+ end),
+ receive {Seq, SeqResult} ->
+ receive {SeqR, SeqRResult} ->
+ {{Seq, SeqResult}, {SeqR, SeqRResult}}
+ end
+ end.
+
+
+
+%%%---------------------------------------------------------------------
+
+imm_tail_seq(doc) ->
+ ["Tests a tail recursive variant of lists:seq/3 ",
+ "with immediate trace to profile"];
+imm_tail_seq(suite) ->
+ [];
+imm_tail_seq(Config) when is_list(Config) ->
+ ?line Timetrap = ?t:timetrap(?t:seconds(10)),
+ ?line PrivDir = ?config(priv_dir, Config),
+ ?line AnalysisFile =
+ filename:join(PrivDir, ?MODULE_STRING"_imm_tail_seq.analysis"),
+ ?line Start = 1,
+ ?line Stop = 1000,
+ ?line Succ = fun (X) -> X + 1 end,
+ ?line ok = fprof:stop(kill),
+ ?line catch eprof:stop(),
+ %%
+ ?line TS0 = erlang:now(),
+ ?line R0 = seq_r(Start, Stop, Succ),
+ ?line TS1 = erlang:now(),
+ %%
+ ?line profiling = eprof:start_profiling([self()]),
+ ?line TS2 = erlang:now(),
+ ?line R2 = seq_r(Start, Stop, Succ),
+ ?line TS3 = erlang:now(),
+ ?line profiling_stopped = eprof:stop_profiling(),
+ ?line R2 = R0,
+ %%
+ ?line eprof:analyse(),
+ ?line stopped = eprof:stop(),
+ %%
+ ?line {ok, Tracer} = fprof:profile(start),
+ ?line ok = fprof:trace([start, {tracer, Tracer}]),
+ ?line TS4 = erlang:now(),
+ ?line R4 = seq_r(Start, Stop, Succ),
+ ?line TS5 = erlang:now(),
+ ?line ok = fprof:trace(stop),
+ ?line ok = fprof:analyse(),
+ ?line ok = fprof:analyse(dest, AnalysisFile),
+ ?line ok = fprof:stop(),
+ ?line R4 = R0,
+ %%
+ ?line {ok, [T, P]} = parse(AnalysisFile),
+ ?line io:format("~p~n~n~p~n", [P, ets:tab2list(T)]),
+ ?line ok = verify(T, P),
+ ?line Proc = pid_to_list(self()),
+ ?line case P of
+ [{analysis_options, _},
+ [{totals, _, Acc, _}],
+ [{Proc, _, undefined, _} | _]] ->
+ ok
+ end,
+ %%
+ ?line ets:delete(T),
+ ?line file:delete(AnalysisFile),
+ ?line ?t:timetrap_cancel(Timetrap),
+ ?line Acc1 = ts_sub(TS1, TS0),
+ ?line Acc3 = ts_sub(TS3, TS2),
+ ?line Acc5 = ts_sub(TS5, TS4),
+ ?line io:format("~p (plain), ~p (eprof), ~p (fprof), ~p (cpu)~n",
+ [Acc1/1000, Acc3/1000, Acc5/1000, Acc/1000]),
+ {comment, io_lib:format("~p/~p (fprof/eprof) times slower",
+ [Acc5/Acc1, Acc3/Acc1])}.
+
+%%%---------------------------------------------------------------------
+
+imm_create_file_slow(doc) ->
+ ["Tests a tail recursive variant of lists:seq/3 ",
+ "with immediate trace to profile"];
+imm_create_file_slow(suite) ->
+ [];
+imm_create_file_slow(Config) when is_list(Config) ->
+ ?line Timetrap = ?t:timetrap(?t:seconds(60)),
+ ?line PrivDir = ?config(priv_dir, Config),
+ ?line DataFile =
+ filename:join(PrivDir, ?MODULE_STRING"_imm_create_file_slow.data"),
+ ?line AnalysisFile =
+ filename:join(PrivDir, ?MODULE_STRING"_imm_create_file_slow.analysis"),
+ ?line ok = fprof:stop(kill),
+ %%
+ ?line TS0 = erlang:now(),
+ ?line ok = create_file_slow(DataFile, 1024),
+ ?line TS1 = erlang:now(),
+ ?line ok = file:delete(DataFile),
+ %%
+ ?line {ok, Tracer} = fprof:profile(start),
+ ?line TS2 = erlang:now(),
+ ?line ok = fprof:apply(?MODULE, create_file_slow, [DataFile, 1024],
+ [{tracer, Tracer}, continue]),
+ ?line TS3 = erlang:now(),
+ ?line ok = fprof:profile(stop),
+ ?line ok = fprof:analyse(),
+ ?line ok = fprof:analyse(dest, AnalysisFile),
+ ?line ok = fprof:stop(),
+ %%
+ ?line {ok, [T, P]} = parse(AnalysisFile),
+ ?line io:format("~p~n~n~p~n", [P, ets:tab2list(T)]),
+ ?line ok = verify(T, P),
+ ?line Proc = pid_to_list(self()),
+ ?line case P of
+ [{analysis_options, _},
+ [{totals, _, Acc, _}],
+ [{Proc, _, undefined, _} | _]] ->
+ ok
+ end,
+ %%
+ ?line ets:delete(T),
+ ?line file:delete(DataFile),
+ ?line file:delete(AnalysisFile),
+ ?line ?t:timetrap_cancel(Timetrap),
+ ?line Acc1 = ts_sub(TS1, TS0),
+ ?line Acc3 = ts_sub(TS3, TS2),
+ ?line io:format("ts:~w, fprof:~w, bare:~w.~n", [Acc, Acc3, Acc1]),
+ {comment, io_lib:format("~p times slower", [Acc3/Acc1])}.
+
+%%%---------------------------------------------------------------------
+
+imm_compile(doc) ->
+ ["Tests to compile a small source file ",
+ "with immediate trace to profile"];
+imm_compile(suite) ->
+ [];
+imm_compile(Config) when is_list(Config) ->
+ ?line Timetrap = ?t:timetrap(?t:minutes(20)),
+ ?line DataDir = ?config(data_dir, Config),
+ ?line SourceFile = filename:join(DataDir, "foo.erl"),
+ ?line PrivDir = ?config(priv_dir, Config),
+ ?line AnalysisFile =
+ filename:join(PrivDir, ?MODULE_STRING"_imm_compile.analysis"),
+ ?line ok = fprof:stop(kill),
+ ?line catch eprof:stop(),
+ %%
+ ?line {ok, foo, _} = compile:file(SourceFile, [binary]),
+ ?line TS0 = erlang:now(),
+ ?line {ok, foo, _} = compile:file(SourceFile, [binary]),
+ ?line TS1 = erlang:now(),
+ %%
+ ?line profiling = eprof:start_profiling([self()]),
+ ?line TS2 = erlang:now(),
+ ?line {ok, foo, _} = compile:file(SourceFile, [binary]),
+ ?line TS3 = erlang:now(),
+ ?line profiling_stopped = eprof:stop_profiling(),
+ %%
+ ?line eprof:analyse(),
+ ?line stopped = eprof:stop(),
+ %%
+ ?line {ok, Tracer} = fprof:profile(start),
+ ?line ok = fprof:trace([start, {tracer, Tracer}]),
+ ?line TS4 = erlang:now(),
+ ?line {ok, foo, _} = compile:file(SourceFile, [binary]),
+ ?line TS5 = erlang:now(),
+ ?line ok = fprof:trace(stop),
+ %%
+ ?line io:format("Analysing...~n"),
+ ?line ok = fprof:analyse(dest, AnalysisFile),
+ ?line ok = fprof:stop(),
+ %%
+ ?line {ok, [T, P]} = parse(AnalysisFile),
+ ?line io:format("~p~n", [P]),
+ ?line Acc1 = ts_sub(TS1, TS0),
+ ?line Acc3 = ts_sub(TS3, TS2),
+ ?line Acc5 = ts_sub(TS5, TS4),
+ ?line io:format("Verifying...~n"),
+ ?line ok = verify(T, P),
+ ?line case P of
+ [{analysis_options, _},
+ [{totals, _, Acc, _}] | _] ->
+ ok
+ end,
+ %%
+ ?line ets:delete(T),
+ ?line file:delete(AnalysisFile),
+ ?line ?t:timetrap_cancel(Timetrap),
+ ?line io:format("~p (plain), ~p (eprof), ~p (fprof), ~p(cpu)~n",
+ [Acc1/1000, Acc3/1000, Acc5/1000, Acc/1000]),
+ {comment, io_lib:format("~p/~p (fprof/eprof) times slower",
+ [Acc5/Acc1, Acc3/Acc1])}.
+
+%%%---------------------------------------------------------------------
+
+cpu_create_file_slow(doc) ->
+ ["Tests the create_file_slow benchmark using cpu_time"];
+cpu_create_file_slow(suite) ->
+ [];
+cpu_create_file_slow(Config) when is_list(Config) ->
+ ?line Timetrap = ?t:timetrap(?t:seconds(40)),
+ ?line PrivDir = ?config(priv_dir, Config),
+ ?line TraceFile =
+ filename:join(PrivDir, ?MODULE_STRING"_cpu_create_file_slow.trace"),
+ ?line AnalysisFile =
+ filename:join(PrivDir, ?MODULE_STRING"_cpu_create_file_slow.analysis"),
+ ?line DataFile =
+ filename:join(PrivDir, ?MODULE_STRING"_cpu_create_file_slow.data"),
+ ?line ok = fprof:stop(kill),
+ %%
+ ?line TS0 = erlang:now(),
+ ?line Result = (catch fprof:apply(?MODULE, create_file_slow,
+ [DataFile, 1024],
+ [{file, TraceFile}, cpu_time])),
+ ?line TS1 = erlang:now(),
+ ?line TestResult =
+ case Result of
+ ok ->
+ ?line ok = fprof:profile(file, TraceFile),
+ ?line ok = fprof:analyse(),
+ ?line ok = fprof:analyse(dest, AnalysisFile),
+ ?line ok = fprof:stop(),
+ %%
+ ?line {ok, [T, P]} = parse(AnalysisFile),
+ ?line io:format("~p~n~n~p~n", [P, ets:tab2list(T)]),
+ ?line ok = verify(T, P),
+ ?line Proc = pid_to_list(self()),
+ ?line case P of
+ [{analysis_options, _},
+ [{totals, _, Acc, _}],
+ [{Proc, _, undefined, _} | _]] ->
+ ok
+ end,
+ %%
+ ?line check_own_and_acc(TraceFile,AnalysisFile),
+ %%
+ ?line ets:delete(T),
+ ?line file:delete(DataFile),
+ ?line file:delete(TraceFile),
+ ?line file:delete(AnalysisFile),
+ ?line Acc1 = ts_sub(TS1, TS0),
+ ?line io:format("cpu_ts:~w, fprof:~w~n", [Acc, Acc1]),
+ {comment, io_lib:format("~p% cpu utilization",
+ [100*Acc/Acc1])};
+ {'EXIT', not_supported} ->
+ case {os:type(), os:version()} of
+ {{unix, sunos}, {Major, Minor, _}}
+ when Major >= 5, Minor >= 7 ->
+ test_server:fail(Result);
+ _ ->
+ {skipped, "not_supported"}
+ end;
+ _ ->
+ test_server:fail(Result)
+ end,
+ ?line ?t:timetrap_cancel(Timetrap),
+ TestResult.
+
+
+
+%%%---------------------------------------------------------------------
+%%% Functions to test
+%%%---------------------------------------------------------------------
+
+
+
+%% Stack recursive seq
+seq(Stop, Stop, Succ) when is_function(Succ) ->
+ [Stop];
+seq(Start, Stop, Succ) when is_function(Succ) ->
+ [Start | seq(Succ(Start), Stop, Succ)].
+
+
+
+%% Tail recursive seq, result list is reversed
+seq_r(Start, Stop, Succ) when is_function(Succ) ->
+ seq_r(Start, Stop, Succ, []).
+
+seq_r(Stop, Stop, _, R) ->
+ [Stop | R];
+seq_r(Start, Stop, Succ, R) ->
+ seq_r(Succ(Start), Stop, Succ, [Start | R]).
+
+
+
+create_file_slow(Name, N) when is_integer(N), N >= 0 ->
+ {ok, FD} =
+ file:open(Name, [raw, write, delayed_write, binary]),
+ if N > 256 ->
+ ok = file:write(FD,
+ lists:map(fun (X) -> <<X:32/unsigned>> end,
+ lists:seq(0, 255))),
+ ok = create_file_slow(FD, 256, N);
+ true ->
+ ok = create_file_slow(FD, 0, N)
+ end,
+ ok = file:close(FD).
+
+create_file_slow(_FD, M, M) ->
+ ok;
+create_file_slow(FD, M, N) ->
+ ok = file:write(FD, <<M:32/unsigned>>),
+ create_file_slow(FD, M+1, N).
+
+
+
+%%%---------------------------------------------------------------------
+%%% Profile verification functions
+%%%---------------------------------------------------------------------
+
+
+
+verify(Tab, [{analysis_options, _},
+ [{totals, Cnt, Acc, Own} | _] | Processes]) ->
+ Processes_1 =
+ lists:map(
+ fun ([{Proc, Cnt_P, undefined, Own_P} | _]) ->
+ case sum_process(Tab, Proc) of
+ {Proc, Cnt_P, Acc_P, Own_P} = Clocks
+ when Acc_P >= Own_P ->
+ Clocks;
+ Weird ->
+ throw({error, [?MODULE, ?LINE, Weird]})
+ end
+ end,
+ Processes),
+ case lists:foldl(
+ fun ({_, Cnt_P2, Acc_P2, Own_P2},
+ {totals, Cnt_T, Acc_T, Own_T}) ->
+ {totals, Cnt_P2+Cnt_T, Acc_P2+Acc_T, Own_P2+Own_T}
+ end,
+ {totals, 0, 0, 0},
+ Processes_1) of
+ {totals, Cnt, Acc_T, Own} when Acc_T >= Acc ->
+ ok;
+ Weird ->
+ throw({error, [?MODULE, ?LINE, Weird]})
+ end.
+
+
+
+sum_process(Tab, Proc) ->
+ ets_select_fold(
+ Tab, [{{{Proc, '_'}, '_'}, [], ['$_']}], 100,
+ fun ({{P, MFA}, {Callers, {MFA, Cnt, Acc, Own}, Called}},
+ {P, Cnt_P, Acc_P, Own_P}) when P == Proc ->
+ ok = verify_callers(Tab, Proc, MFA, Callers),
+ ok = verify_called(Tab, Proc, MFA, Called),
+ {P, Cnt+Cnt_P, Acc+Acc_P, Own+Own_P};
+ (Weird, Clocks) ->
+ throw({error, [?MODULE, ?LINE, Weird, Clocks]})
+ end,
+ {Proc, 0, 0, 0}).
+
+verify_callers(_, _, _, []) ->
+ ok;
+verify_callers(Tab, Proc, MFA, [{Caller, Cnt, Acc, Own} | Tail]) ->
+ Id = {Proc, Caller},
+ case ets:lookup(Tab, Id) of
+ [{Id, {_, {Caller, _, _, _}, Called}}] ->
+ case lists:keysearch(MFA, 1, Called) of
+ {value, {MFA, Cnt, Acc, Own}} ->
+ verify_callers(Tab, Proc, MFA, Tail);
+ false ->
+ throw({error, [?MODULE, ?LINE, MFA, Id]})
+ end;
+ Weird ->
+ throw({error, [?MODULE, ?LINE, Weird]})
+ end.
+
+verify_called(_, _, _, []) ->
+ ok;
+verify_called(Tab, Proc, MFA, [{Called, Cnt, Acc, Own} | Tail]) ->
+ Id = {Proc, Called},
+ case ets:lookup(Tab, Id) of
+ [{Id, {Callers, {Called, _, _, _}, _}}] ->
+ case lists:keysearch(MFA, 1, Callers) of
+ {value, {MFA, Cnt, Acc, Own}} ->
+ verify_called(Tab, Proc, MFA, Tail);
+ false ->
+ throw({error, [?MODULE, ?LINE, MFA, Id]})
+ end;
+ Weird ->
+ throw({error, [?MODULE, ?LINE, Weird]})
+ end.
+
+
+
+%% Parse a analysis file and return an Ets table with all function entries,
+%% and a list of process entries. Checks the concistency of the function
+%% entries when they are read.
+parse(Filename) ->
+ case file:open(Filename, [read]) of
+ {ok, FD} ->
+ Result = parse_stream(FD),
+ file:close(FD),
+ Result;
+ Error ->
+ Error
+ end.
+
+parse_stream(FD) ->
+ Tab = ets:new(fprof_SUITE, []),
+ parse_stream(FD, Tab, [], void).
+
+parse_stream(FD, Tab, R, Proc) ->
+ case catch io:read(FD, '') of
+ {'EXIT', _} ->
+ {error, [?MODULE, ?LINE]};
+ {ok, Term} ->
+ case parse_term(Term) of
+ {ok, {analysis_options, _} = Term_1}
+ when Proc == void ->
+ parse_stream(FD, Tab, [Term_1 | R], analysis_options);
+ {ok, [{totals, _, _, _} | _] = Term_1}
+ when Proc == analysis_options ->
+ parse_stream(FD, Tab, [Term_1 | R], totals);
+ {ok, [{P, _, _, _} | _] = Term_1} ->
+ parse_stream(FD, Tab, [Term_1 | R], P);
+ {ok, {_Callers, {MFA, _, _, _}, _Called} = Term_1}
+ when Proc == totals; is_list(Proc) ->
+ ets:insert(Tab, {{Proc, MFA}, Term_1}),
+ parse_stream(FD, Tab, R, Proc);
+ {ok, Term_1} ->
+ {error, [?MODULE, ?LINE, Term_1]};
+ E ->
+ E
+ end;
+ eof ->
+ {ok, [Tab, lists:reverse(R)]};
+ Error ->
+ Error
+ end.
+
+parse_term({Callers, Func, Called})
+ when is_list(Callers), is_list(Called) ->
+ Callers_1 = lists:map(fun parse_clocks/1, Callers),
+ Func_1 = parse_clocks(Func),
+ Called_1 = lists:map(fun parse_clocks/1, Called),
+ Result = {Callers_1, Func_1, Called_1},
+ case chk_invariant(Result) of
+ ok ->
+ {ok, Result};
+ Error ->
+ Error
+ end;
+parse_term([{_, _, _, _} = Clocks | Tail]) ->
+ {ok, [parse_clocks(Clocks) | Tail]};
+parse_term(Term) ->
+ {ok, Term}.
+
+parse_clocks({MFA, Cnt, undefined, Own}) ->
+ {MFA, Cnt, undefined, round(Own*1000)};
+parse_clocks({MFA, Cnt, Acc, Own}) ->
+ {MFA, Cnt, round(Acc*1000), round(Own*1000)};
+parse_clocks(Clocks) ->
+ Clocks.
+
+
+
+chk_invariant({Callers, {MFA, Cnt, Acc, Own}, Called} = Term) ->
+ {_, Callers_Cnt, Callers_Acc, Callers_Own} = Callers_Sum = sum(Callers),
+% {_, Called_Cnt, Called_Acc, Called_Own} = Called_Sum = sum(Called),
+ case {MFA,
+ lists:keymember(suspend, 1, Callers),
+ lists:keymember(garbage_collect, 1, Callers),
+ Called} of
+ {suspend, false, _, []} ->
+ ok;
+ {suspend, _, _, _} = Weird ->
+ {error, [?MODULE, ?LINE, Weird, Term]};
+ {garbage_collect, false, false, []} ->
+ ok;
+ {garbage_collect, false, false, [{suspend, _, _, _}]} ->
+ ok;
+ {garbage_collect, _, _, _} = Weird ->
+ {error, [?MODULE, ?LINE, Weird, Term]};
+ {undefined, false, false, _}
+ when Callers == [], Cnt == 0, Acc == 0, Own == 0 ->
+ ok;
+ {undefined, _, _, _} = Weird ->
+ {error, [?MODULE, ?LINE, Weird, Term]};
+ {_, _, _, _} ->
+ case chk_self_call(Term) of
+ true when Callers_Cnt /= Cnt; Callers_Acc /= Acc;
+ Callers_Own /= Own ->
+ {error, [?MODULE, ?LINE, Callers_Sum, Term]};
+% true when Called_Acc + Own /= Acc ->
+% io:format("WARNING: ~p:~p, ~p, ~p.~n",
+% [?MODULE, ?LINE, Term, Called_Sum]),
+% {error, [?MODULE, ?LINE, Term, Called_Sum]};
+% ok;
+ true ->
+ ok;
+ false ->
+ {error, [?MODULE, ?LINE, Term]}
+ end
+ end.
+
+ts_sub({A, B, C}, {A0, B0, C0}) ->
+ ((A - A0)*1000000000000 + (B - B0))*1000000 + C - C0.
+
+sum(Funcs) ->
+ {sum, _Cnt, _Acc, _Own} =
+ lists:foldl(
+ fun ({_, C1, A1, O1}, {sum, C2, A2, O2}) ->
+ {sum, C1+C2, A1+A2, O1+O2}
+ end,
+ {sum, 0, 0, 0},
+ Funcs).
+
+chk_self_call({Callers, {MFA, _Cnt, _Acc, _Own}, Called}) ->
+ case lists:keysearch(MFA, 1, Callers) of
+ false ->
+ true;
+ {value, {MFA, C, 0, O}} ->
+ case lists:keysearch(MFA, 1, Called) of
+ false ->
+ false;
+ {value, {MFA, C, 0, O}} ->
+ true;
+ {value, _} ->
+ false
+ end;
+ {value, _} ->
+ false
+ end.
+
+
+
+%%%---------------------------------------------------------------------
+%%% Fairly generic support functions
+%%%---------------------------------------------------------------------
+
+
+ets_select_fold(Table, MatchSpec, Limit, Fun, Acc) ->
+ ets:safe_fixtable(Table, true),
+ ets_select_fold_1(ets:select(Table, MatchSpec, Limit), Fun, Acc).
+
+ets_select_fold_1('$end_of_table', _, Acc) ->
+ Acc;
+ets_select_fold_1({Matches, Continuation}, Fun, Acc) ->
+ ets_select_fold_1(ets:select(Continuation),
+ Fun,
+ lists:foldl(Fun, Acc, Matches)).
+
+
+
+% ets_select_foreach(Table, MatchSpec, Limit, Fun) ->
+% ets:safe_fixtable(Table, true),
+% ets_select_foreach_1(ets:select(Table, MatchSpec, Limit), Fun).
+
+% ets_select_foreach_1('$end_of_table', _) ->
+% ok;
+% ets_select_foreach_1({Matches, Continuation}, Fun) ->
+% lists:foreach(Fun, Matches),
+% ets_select_foreach_1(ets:select(Continuation), Fun).
+
+
+%%%---------------------------------------------------------------------
+%%% Simple smulation of fprof used for checking own and acc times for
+%%% each function.
+%%% The function 'undefined' is ignored
+%%%---------------------------------------------------------------------
+
+%% check_own_and_acc_traced(TraceFile, AnalysisFile) ->
+%% check_own_and_acc(TraceFile, AnalysisFile, fun handle_trace_traced/2).
+
+check_own_and_acc(TraceFile, AnalysisFile) ->
+ check_own_and_acc(TraceFile, AnalysisFile, fun handle_trace/2).
+
+check_own_and_acc(TraceFile, AnalysisFile, HandlerFun) ->
+ dbg:trace_client(file,TraceFile,{HandlerFun,{init,self()}}),
+ receive {result,Result} ->
+ compare(Result,get_own_and_acc_from_analysis(AnalysisFile))
+ end.
+
+%% handle_trace_traced(Trace, Msg) ->
+%% io:format("handle_trace_traced(~p, ~p).", [Trace, Msg]),
+%% handle_trace(Trace, Msg).
+
+handle_trace(Trace,{init,Parent}) ->
+ ?dbg("~p",[start]),
+ ets:new(fprof_verify_tab,[named_table]),
+ handle_trace(Trace,Parent);
+handle_trace({trace_ts,Pid,in,MFA,TS},P) ->
+ ?dbg("~p",[{{in,Pid,MFA},get(Pid)}]),
+ case get(Pid) of
+ [suspend|[suspend|_]=NewStack] ->
+ T = ts_sub(TS,get({Pid,last_ts})),
+ update_acc(Pid,NewStack,T),
+ put(Pid,NewStack);
+ [suspend|NewStack] = Stack ->
+ T = ts_sub(TS,get({Pid,last_ts})),
+ update_acc(Pid,Stack,T),
+ put(Pid,NewStack);
+ [] ->
+ put(Pid,[MFA]),
+ insert(Pid,MFA);
+ undefined ->
+ put(first_ts,TS),
+ put(Pid,[MFA]),
+ insert(Pid,MFA)
+ end,
+ put({Pid,last_ts},TS),
+ P;
+handle_trace({trace_ts,Pid,out,_MfaOrZero,TS},P) ->
+ ?dbg("~p",[{{out,Pid,_MfaOrZero},get(Pid)}]),
+ T = ts_sub(TS,get({Pid,last_ts})),
+ case get(Pid) of
+ [suspend|S] = Stack ->
+ update_acc(Pid,S,T),
+ put(Pid,[suspend|Stack]);
+ [MFA|_] = Stack ->
+ insert(Pid,suspend),
+ update_own(Pid,MFA,T),
+ update_acc(Pid,Stack,T),
+ put(Pid,[suspend|Stack]);
+ [] ->
+ insert(Pid,suspend),
+ put(Pid,[suspend])
+ end,
+ put({Pid,last_ts},TS),
+ P;
+handle_trace({trace_ts,Pid,call,MFA,{cp,Caller},TS},P) ->
+ ?dbg("~p",[{{call,Pid,MFA},get(Pid)}]),
+ T = ts_sub(TS,get({Pid,last_ts})),
+ case get(Pid) of
+ [MFA|_] = Stack ->
+ %% recursive
+ update_own(Pid,MFA,T),
+ update_acc(Pid,Stack,T);
+ [CallingMFA|_] = Stack when Caller==undefined ->
+ insert(Pid,MFA),
+ update_own(Pid,CallingMFA,T),
+ update_acc(Pid,Stack,T),
+ put(Pid,[MFA|Stack]);
+ [] when Caller==undefined ->
+ insert(Pid,MFA),
+ insert(Pid,MFA),
+ put(Pid,[MFA]);
+ Stack0 ->
+ Stack = [CallingMFA|_] = insert_caller(Caller,Stack0,[]),
+ insert(Pid,MFA),
+ insert(Pid,Caller),
+ update_own(Pid,CallingMFA,T),
+ update_acc(Pid,Stack,T),
+ put(Pid,[MFA|Stack])
+ end,
+ put({Pid,last_ts},TS),
+ P;
+handle_trace({trace_ts,Pid,return_to,MFA,TS},P) ->
+ ?dbg("~p",[{{return_to,Pid,MFA},get(Pid)}]),
+ T = ts_sub(TS,get({Pid,last_ts})),
+ case get(Pid) of
+ [MFA|_] = Stack ->
+ %% recursive
+ update_own(Pid,MFA,T),
+ update_acc(Pid,Stack,T),
+ put(Pid,Stack);
+ [ReturnFromMFA,MFA|RestOfStack] = Stack ->
+ update_own(Pid,ReturnFromMFA,T),
+ update_acc(Pid,Stack,T),
+ put(Pid,[MFA|RestOfStack]);
+ [ReturnFromMFA|RestOfStack] = Stack ->
+ update_own(Pid,ReturnFromMFA,T),
+ update_acc(Pid,Stack,T),
+ case find_return_to(MFA,RestOfStack) of
+ [] when MFA==undefined ->
+ put(Pid,[]);
+ [] ->
+ insert(Pid,MFA),
+ put(Pid,[MFA]);
+ NewStack ->
+ put(Pid,NewStack)
+ end
+ end,
+ put({Pid,last_ts},TS),
+ P;
+handle_trace({trace_ts,Pid,gc_start,_,TS},P) ->
+ ?dbg("~p",[{{gc_start,Pid},get(Pid)}]),
+ case get(Pid) of
+ [suspend|_] = Stack ->
+ T = ts_sub(TS,get({Pid,last_ts})),
+ insert(Pid,garbage_collect),
+ update_acc(Pid,Stack,T),
+ put(Pid,[garbage_collect|Stack]);
+ [CallingMFA|_] = Stack ->
+ T = ts_sub(TS,get({Pid,last_ts})),
+ insert(Pid,garbage_collect),
+ update_own(Pid,CallingMFA,T),
+ update_acc(Pid,Stack,T),
+ put(Pid,[garbage_collect|Stack]);
+ undefined ->
+ put(first_ts,TS),
+ put(Pid,[garbage_collect]),
+ insert(Pid,garbage_collect)
+ end,
+ put({Pid,last_ts},TS),
+ P;
+handle_trace({trace_ts,Pid,gc_end,_,TS},P) ->
+ ?dbg("~p",[{{gc_end,Pid},get(Pid)}]),
+ T = ts_sub(TS,get({Pid,last_ts})),
+ case get(Pid) of
+ [garbage_collect|RestOfStack] = Stack ->
+ update_own(Pid,garbage_collect,T),
+ update_acc(Pid,Stack,T),
+ put(Pid,RestOfStack)
+ end,
+ put({Pid,last_ts},TS),
+ P;
+handle_trace({trace_ts,Pid,spawn,NewPid,{M,F,Args},TS},P) ->
+ MFA = {M,F,length(Args)},
+ ?dbg("~p",[{{spawn,Pid,NewPid,MFA},get(Pid)}]),
+ T = ts_sub(TS,get({Pid,last_ts})),
+ put({NewPid,last_ts},TS),
+ put(NewPid,[suspend,MFA]),
+ insert(NewPid,suspend),
+ insert(NewPid,MFA),
+ case get(Pid) of
+ [SpawningMFA|_] = Stack ->
+ update_own(Pid,SpawningMFA,T),
+ update_acc(Pid,Stack,T)
+ end,
+ put({Pid,last_ts},TS),
+ P;
+handle_trace({trace_ts,Pid,exit,_Reason,TS},P) ->
+ ?dbg("~p",[{{exit,Pid,_Reason},get(Pid)}]),
+ T = ts_sub(TS,get({Pid,last_ts})),
+ case get(Pid) of
+ [DyingMFA|_] = Stack ->
+ update_own(Pid,DyingMFA,T),
+ update_acc(Pid,Stack,T),
+ put(Pid,[]);
+ [] ->
+ ok
+ end,
+ put({Pid,last_ts},TS),
+ P;
+handle_trace({trace_ts,_,Link,_,_},P)
+ when Link==link;
+ Link==unlink;
+ Link==getting_linked;
+ Link==getting_unlinked ->
+ P;
+handle_trace(end_of_trace,P) ->
+ ?dbg("~p",['end']),
+ Result = ets:tab2list(fprof_verify_tab),
+ {TotOwn,ProcOwns} = get_proc_owns(Result,[],0),
+ TotAcc = ts_sub(get_last_ts(),get(first_ts)),
+ P ! {result,[{totals,TotAcc,TotOwn}|ProcOwns]++Result},
+ P;
+handle_trace(Other,_P) ->
+ exit({unexpected,Other}).
+
+find_return_to(MFA,[MFA|_]=Stack) ->
+ Stack;
+find_return_to(MFA,[_|Stack]) ->
+ find_return_to(MFA,Stack);
+find_return_to(_MFA,[]) ->
+ [].
+
+insert_caller(MFA,[MFA|Rest],Result) ->
+ lists:reverse(Result)++[MFA|Rest];
+insert_caller(MFA,[Other|Rest],Result) ->
+ insert_caller(MFA,Rest,[Other|Result]);
+insert_caller(MFA,[],Result) ->
+ lists:reverse([MFA|Result]).
+
+insert(Pid,MFA) ->
+ case ets:member(fprof_verify_tab,{Pid,MFA}) of
+ false ->
+ ets:insert(fprof_verify_tab,{{Pid,MFA},0,0});
+ true ->
+ ok
+ end.
+
+update_own(Pid,MFA,T) ->
+ ets:update_counter(fprof_verify_tab,{Pid,MFA},{3,T}).
+
+update_acc(Pid,[MFA|Rest],T) ->
+ case lists:member(MFA,Rest) of
+ true ->
+ %% Only charge one time for recursive functions
+ ok;
+ false ->
+ ets:update_counter(fprof_verify_tab,{Pid,MFA},{2,T})
+ end,
+ update_acc(Pid,Rest,T);
+update_acc(_Pid,[],_T) ->
+ ok.
+
+
+get_last_ts() ->
+ get_last_ts(get(),{0,0,0}).
+get_last_ts([{{_,last_ts},TS}|Rest],Last) when TS>Last ->
+ get_last_ts(Rest,TS);
+get_last_ts([_|Rest],Last) ->
+ get_last_ts(Rest,Last);
+get_last_ts([],Last) ->
+ Last.
+
+get_proc_owns([{{Pid,_MFA},_Acc,Own}|Rest],Result,Sum) ->
+ NewResult =
+ case lists:keysearch(Pid,1,Result) of
+ {value,{Pid,undefined,PidOwn}} ->
+ lists:keyreplace(Pid,1,Result,{Pid,undefined,PidOwn+Own});
+ false ->
+ [{Pid,undefined,Own}|Result]
+ end,
+ get_proc_owns(Rest,NewResult,Sum+Own);
+get_proc_owns([],Result,Sum) ->
+ {Sum,Result}.
+
+
+compare([X|Rest],FprofResult) ->
+ FprofResult1 =
+ case lists:member(X,FprofResult) of
+ true ->
+ ?dbg("~p",[X]),
+ lists:delete(X,FprofResult);
+ false ->
+ case lists:keysearch(element(1,X),1,FprofResult) of
+ {value,Fprof} ->
+ put(compare_error,true),
+ io:format("Error: Different values\n"
+ "Fprof: ~p\n"
+ "Simulator: ~p",[Fprof,X]),
+ lists:delete(Fprof,FprofResult);
+ false ->
+ put(compare_error,true),
+ io:format("Error: Missing in fprof: ~p",[X]),
+ FprofResult
+ end
+ end,
+ compare(Rest,FprofResult1);
+compare([],Rest) ->
+ case {remove_undefined(Rest,[]),get(compare_error)} of
+ {[],undefined} -> ok;
+ {Error,_} ->
+ case Error of
+ [] -> ok;
+ _ -> io:format("\nMissing in simulator results:\n~p\n",[Error])
+ end,
+ ?t:fail({error,mismatch_between_simulator_and_fprof})
+ end.
+
+remove_undefined([{{_Pid,undefined},_,_}|Rest],Result) ->
+ remove_undefined(Rest,Result);
+remove_undefined([X|Rest],Result) ->
+ remove_undefined(Rest,[X|Result]);
+remove_undefined([],Result) ->
+ Result.
+
+get_own_and_acc_from_analysis(Log) ->
+ case file:consult(Log) of
+ {ok,[_Options,[{totals,_,TotAcc,TotOwn}]|Rest]} ->
+ get_own_and_acc(undefined,Rest,
+ [{totals,m1000(TotAcc),m1000(TotOwn)}]);
+ Error ->
+ exit({error,{cant_open,Log,Error}})
+ end.
+
+get_own_and_acc(_,[[{PidStr,_,Acc,Own}|_]|Rest],Result) ->
+ Pid = list_to_pid(PidStr),
+ get_own_and_acc(Pid,Rest,[{Pid,m1000(Acc),m1000(Own)}|Result]);
+get_own_and_acc(Pid,[{_Callers,{MFA,_,Acc,Own},_Called}|Rest],Result) ->
+ get_own_and_acc(Pid,Rest,[{{Pid,MFA},m1000(Acc),m1000(Own)}|Result]);
+get_own_and_acc(_,[],Result) ->
+ lists:reverse(Result).
+
+m1000(undefined) ->
+ undefined;
+m1000(X) ->
+ round(X*1000).
+
diff --git a/lib/tools/test/fprof_SUITE_data/foo.erl b/lib/tools/test/fprof_SUITE_data/foo.erl
new file mode 100644
index 0000000000..eaa8132b1e
--- /dev/null
+++ b/lib/tools/test/fprof_SUITE_data/foo.erl
@@ -0,0 +1,41 @@
+%% ``The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved via the world wide web at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
+%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
+%% AB. All Rights Reserved.''
+%%
+%% $Id$
+%%
+-module(foo).
+
+-export([create_file_slow/2]).
+
+
+
+create_file_slow(Name, N) when integer(N), N >= 0 ->
+ {ok, FD} =
+ file:open(Name, [raw, write, delayed_write, binary]),
+ if N > 256 ->
+ ok = file:write(FD,
+ lists:map(fun (X) -> <<X:32/unsigned>> end,
+ lists:seq(0, 255))),
+ ok = create_file_slow(FD, 256, N);
+ true ->
+ ok = create_file_slow(FD, 0, N)
+ end,
+ ok = file:close(FD).
+
+create_file_slow(FD, M, M) ->
+ ok;
+create_file_slow(FD, M, N) ->
+ ok = file:write(FD, <<M:32/unsigned>>),
+ create_file_slow(FD, M+1, N).
diff --git a/lib/tools/test/ignore_cores.erl b/lib/tools/test/ignore_cores.erl
new file mode 120000
index 0000000000..8902a469ef
--- /dev/null
+++ b/lib/tools/test/ignore_cores.erl
@@ -0,0 +1 @@
+../../../erts/test/ignore_cores.erl \ No newline at end of file
diff --git a/lib/tools/test/instrument_SUITE.erl b/lib/tools/test/instrument_SUITE.erl
new file mode 100644
index 0000000000..da5930e015
--- /dev/null
+++ b/lib/tools/test/instrument_SUITE.erl
@@ -0,0 +1,129 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1998-2010. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(instrument_SUITE).
+
+-export([all/1,init_per_testcase/2,fin_per_testcase/2]).
+
+-export(['+Mim true'/1, '+Mis true'/1]).
+
+-include("test_server.hrl").
+
+init_per_testcase(_Case, Config) ->
+ ?line Dog=?t:timetrap(10000),
+ [{watchdog, Dog}|Config].
+
+fin_per_testcase(_Case, Config) ->
+ Dog=?config(watchdog, Config),
+ ?t:timetrap_cancel(Dog),
+ ok.
+
+all(suite) -> ['+Mim true', '+Mis true'].
+
+'+Mim true'(doc) -> ["Check that memory data can be read and processed"];
+'+Mim true'(suite) -> [];
+'+Mim true'(Config) when is_list(Config) ->
+ ?line Node = start_slave("+Mim true"),
+ ?line MD = rpc:call(Node, instrument, memory_data, []),
+ ?line [{total,[{sizes,S1,S2,S3},{blocks,B1,B2,B3}]}]
+ = rpc:call(Node, instrument, memory_status, [total]),
+ ?line stop_slave(Node),
+ ?line true = S1 =< S2,
+ ?line true = S2 =< S3,
+ ?line true = B1 =< B2,
+ ?line true = B2 =< B3,
+ ?line MDS = instrument:sort(MD),
+ ?line {Low, High} = instrument:mem_limits(MDS),
+ ?line true = Low < High,
+ ?line {_, AL} = MDS,
+ ?line SumBlocks = instrument:sum_blocks(MD),
+ ?line case SumBlocks of
+ N when is_integer(N) ->
+ ?line N = lists:foldl(fun ({_,_,Size,_}, Sum) ->
+ Size+Sum
+ end,
+ 0,
+ AL),
+ ?line N =< S3;
+ Other ->
+ ?line ?t:fail(Other)
+ end,
+ ?line lists:foldl(
+ fun ({TDescr,Addr,Size,Proc}, MinAddr) ->
+ ?line true = TDescr /= invalid_type,
+ ?line true = is_integer(TDescr),
+ ?line true = is_integer(Addr),
+ ?line true = is_integer(Size),
+ ?line true = Addr >= MinAddr,
+ ?line case Proc of
+ {0, Number, Serial} ->
+ ?line true = is_integer(Number),
+ ?line true = is_integer(Serial);
+ undefined ->
+ ok;
+ BadProc ->
+ ?line ?t:fail({badproc, BadProc})
+ end,
+ ?line NextMinAddr = Addr+Size,
+ ?line true = NextMinAddr =< High,
+ ?line NextMinAddr
+ end,
+ Low,
+ AL),
+ ?line {_, DAL} = instrument:descr(MDS),
+ ?line lists:foreach(
+ fun ({TDescr,_,_,Proc}) ->
+ ?line true = TDescr /= invalid_type,
+ ?line true = is_atom(TDescr) orelse is_list(TDescr),
+ ?line true = is_pid(Proc) orelse Proc == undefined
+ end,
+ DAL),
+ ?line ASL = lists:map(fun ({_,A,S,_}) -> {A,S} end, AL),
+ ?line ASL = lists:map(fun ({_,A,S,_}) -> {A,S} end, DAL),
+ ?line instrument:holes(MDS),
+ ?line {comment,
+ "total status - sum of blocks = " ++ integer_to_list(S1-SumBlocks)}.
+
+'+Mis true'(doc) -> ["Check that memory data can be read and processed"];
+'+Mis true'(suite) -> [];
+'+Mis true'(Config) when is_list(Config) ->
+ ?line Node = start_slave("+Mis true"),
+ ?line [{total,[{sizes,S1,S2,S3},{blocks,B1,B2,B3}]}]
+ = rpc:call(Node, instrument, memory_status, [total]),
+ ?line true = S1 =< S2,
+ ?line true = S2 =< S3,
+ ?line true = B1 =< B2,
+ ?line true = B2 =< B3,
+ ?line true = is_list(rpc:call(Node,instrument,memory_status,[allocators])),
+ ?line true = is_list(rpc:call(Node,instrument,memory_status,[classes])),
+ ?line true = is_list(rpc:call(Node,instrument,memory_status,[types])),
+ ?line ok.
+
+start_slave(Args) ->
+ ?line {A, B, C} = now(),
+ ?line MicroSecs = A*1000000000000 + B*1000000 + C,
+ ?line Name = "instr_" ++ integer_to_list(MicroSecs),
+ ?line Pa = filename:dirname(code:which(?MODULE)),
+ ?line {ok, Node} = ?t:start_node(list_to_atom(Name),
+ slave,
+ [{args, "-pa " ++ Pa ++ " " ++ Args}]),
+ ?line Node.
+
+
+stop_slave(Node) ->
+ ?line true = ?t:stop_node(Node).
diff --git a/lib/tools/test/make_SUITE.erl b/lib/tools/test/make_SUITE.erl
new file mode 100644
index 0000000000..72dccdb465
--- /dev/null
+++ b/lib/tools/test/make_SUITE.erl
@@ -0,0 +1,295 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2010. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(make_SUITE).
+
+-export([all/1, make_all/1, make_files/1]).
+-export([otp_6057_init/1,
+ otp_6057_a/1, otp_6057_b/1, otp_6057_c/1,
+ otp_6057_end/1]).
+
+-include("test_server.hrl").
+
+-include_lib("kernel/include/file.hrl").
+
+%% in ./make_SUITE_data there are test-files used by this
+%% test suite. There are 4 files named test1.erl ... test5.erl.
+%% The test files are attacked in various ways in order to put make on trial.
+%%
+%% Also, and Emakefile exists in ./make_SUITE_data. This file specifies
+%% that the file :"test5.erl" shall be compiled with the 'S' option,
+%% i.e. produce "test5.S" instead of "test5.<objext>"
+
+all(suite) -> [make_all, make_files,
+ {conf, otp_6057_init,
+ [otp_6057_a,otp_6057_b,otp_6057_c], otp_6057_end}].
+
+test_files() -> ["test1", "test2", "test3", "test4"].
+
+make_all(suite) -> [];
+make_all(Config) when is_list(Config) ->
+ ?line Current = prepare_data_dir(Config),
+ ?line up_to_date = make:all(),
+ ?line ok = ensure_exists(test_files()),
+ ?line ok = ensure_exists(["test5"],".S"), % Emakefile: [{test5,['S']}
+ ?line file:set_cwd(Current),
+ ?line ensure_no_messages(),
+ ok.
+
+make_files(suite) -> [];
+make_files(Config) when is_list(Config) ->
+ ?line Current = prepare_data_dir(Config),
+
+ %% Make files that exist.
+
+ ?line Files = [test1, test2],
+ ?line up_to_date = make:files(Files), % ok files
+ ?line ok = ensure_exists(Files),
+
+ ?line error = make:files([test1,test7]), % non existing file
+ ?line up_to_date = make:files([test1,test2],[debug_info]), % with option
+
+ ?line file:set_cwd(Current),
+ ?line ensure_no_messages(),
+ ok.
+
+
+%% Moves to the data directory of this suite, clean it from any object
+%% files (*.jam for a JAM emulator). Returns the previous directory.
+prepare_data_dir(Config) ->
+ ?line {ok, Current} = file:get_cwd(),
+ ?line {value, {data_dir, Dir}} = lists:keysearch(data_dir, 1, Config),
+ ?line file:set_cwd(Dir),
+ ?line {ok, Files} = file:list_dir("."),
+ ?line delete_obj(Files, code:objfile_extension()),
+ ?line ensure_no_messages(),
+ Current.
+
+delete_obj([File|Rest], ObjExt) ->
+ ?line case filename:extension(File) of
+ ObjExt -> file:delete(File);
+ ".S" -> file:delete(File);
+ _ -> ok
+ end,
+ ?line delete_obj(Rest, ObjExt);
+delete_obj([], _) ->
+ ok.
+
+
+
+%% Ensure that the given object files exists.
+ensure_exists(Names) ->
+ ensure_exists(Names, code:objfile_extension()).
+
+ensure_exists([Name|Rest], ObjExt) when is_atom(Name) ->
+ ensure_exists([atom_to_list(Name)|Rest], ObjExt);
+ensure_exists([Name|Rest], ObjExt) ->
+ case filelib:is_regular(Name++ObjExt) of
+ true ->
+ ensure_exists(Rest, ObjExt);
+ false ->
+ Name++ObjExt
+ end;
+ensure_exists([], _) ->
+ ok.
+
+otp_6057_init(Config) when is_list(Config) ->
+ ?line DataDir = ?config(data_dir, Config),
+ ?line PrivDir = ?config(priv_dir, Config),
+
+ %% Create the directories PrivDir/otp_6057/src1, /src2 and /ebin
+ Src1 = filename:join([PrivDir, otp_6057, src1]),
+ Src2 = filename:join([PrivDir, otp_6057, src2]),
+ Ebin = filename:join([PrivDir, otp_6057, ebin]),
+ ?line ok = file:make_dir(filename:join(PrivDir, otp_6057)),
+ ?line ok = file:make_dir(Src1),
+ ?line ok = file:make_dir(Src2),
+ ?line ok = file:make_dir(Ebin),
+
+ %% Copy test1.erl and test2.erl to src1, and test3.erl to src2
+ Test1orig = filename:join(DataDir, "test1.erl"),
+ Test2orig = filename:join(DataDir, "test2.erl"),
+ Test3orig = filename:join(DataDir, "test3.erl"),
+ Test1 = filename:join(Src1, "test1.erl"),
+ Test2 = filename:join(Src1, "test2.erl"),
+ Test3 = filename:join(Src2, "test3.erl"),
+ ?line {ok, _} = file:copy(Test1orig, Test1),
+ ?line {ok, _} = file:copy(Test2orig, Test2),
+ ?line {ok, _} = file:copy(Test3orig, Test3),
+
+ %% Create an Emakefile in src1
+ Emakefile = filename:join(Src1, "Emakefile"),
+ ?line {ok, Fd} = file:open(Emakefile, write),
+ ?line ok = io:write(Fd, {["test1.erl","test2","../src2/test3"],
+ [{outdir,"../ebin"}]}),
+ ?line ok = io:fwrite(Fd, ".~n", []),
+ ?line ok = file:close(Fd),
+
+ ?line ensure_no_messages(),
+ Config.
+
+otp_6057_a(suite) ->
+ [];
+otp_6057_a(doc) ->
+ ["Test that make:all/0 looks for object file in correct place"];
+otp_6057_a(Config) when is_list(Config) ->
+ ?line PrivDir = ?config(priv_dir, Config),
+
+ %% Go to src1, saving old CWD
+ ?line {ok, CWD} = file:get_cwd(),
+ Src1 = filename:join([PrivDir, otp_6057, src1]),
+ ?line ok = file:set_cwd(Src1),
+
+ %% Call make:all()
+ ?line up_to_date = make:all(),
+
+ %% Ensure that all beam files are created in the ebin directory
+ Ebin = filename:join([PrivDir, otp_6057, ebin]),
+ Test1 = filename:join(Ebin, test1),
+ Test2 = filename:join(Ebin, test2),
+ Test3 = filename:join(Ebin, test3),
+ case ensure_exists([Test1, Test2, Test3]) of
+ ok -> ok;
+ Missing ->
+ ?line ?t:fail({"missing beam file", Missing})
+ end,
+
+ %% Check creation date of test1.beam and make sure it is not
+ %% recompiled if make:all() is called again.
+ %% (Sleep a while, if the file is recompiled within a second then
+ %% mtime will be the same).
+ ?line {ok, FileInfo1} = file:read_file_info(Test1++".beam"),
+ Date1 = FileInfo1#file_info.mtime,
+ ?t:sleep(?t:seconds(2)),
+ ?line up_to_date = make:all(),
+ ?line {ok, FileInfo2} = file:read_file_info(Test1++".beam"),
+ case FileInfo2#file_info.mtime of
+ Date1 -> ok;
+ _Date2 ->
+ ?line ?t:fail({"recompiled beam file", Test1++".beam"})
+ end,
+
+ %% Remove the beam files
+ ?line ok =
+ ensure_removed([Test1++".beam",Test2++".beam",Test2++".beam"]),
+
+ %% Return to original CWD
+ ?line ok = file:set_cwd(CWD),
+
+ ?line ensure_no_messages(),
+ ok.
+
+otp_6057_b(suite) ->
+ [];
+otp_6057_b(doc) ->
+ ["Test that make:files/1 can handle a file in another directory"];
+otp_6057_b(Config) when is_list(Config) ->
+ ?line PrivDir = ?config(priv_dir, Config),
+
+ %% Go to src1, saving old CWD
+ ?line {ok, CWD} = file:get_cwd(),
+ Src1 = filename:join([PrivDir, otp_6057, src1]),
+ ?line ok = file:set_cwd(Src1),
+
+ %% Ensure there is no beam file already
+ Ebin = filename:join([PrivDir, otp_6057, ebin]),
+ Test3 = filename:join(Ebin, "test3"),
+ ?line ok = ensure_removed([Test3++".beam"]),
+
+ %% Call make:files/1
+ ?line up_to_date = make:files(["../src2/test3"]),
+
+ %% Ensure that the beam file is created in the ebin directory
+ case ensure_exists([Test3]) of
+ ok -> ok;
+ Missing ->
+ ?line ?t:fail({"missing beam file", Missing})
+ end,
+
+ %% Remove the beam file
+ ?line ok = ensure_removed([Test3++".beam"]),
+
+ %% Return to original CWD
+ ?line ok = file:set_cwd(CWD),
+
+ ?line ensure_no_messages(),
+ ok.
+
+otp_6057_c(suite) ->
+ [];
+otp_6057_c(doc) ->
+ ["Test that make:files/1 find options in Emakefile if a file is "
+ "given with the .erl extension there"];
+otp_6057_c(Config) when is_list(Config) ->
+ ?line PrivDir = ?config(priv_dir, Config),
+
+ %% Go to src1, saving old CWD
+ ?line {ok, CWD} = file:get_cwd(),
+ Src1 = filename:join([PrivDir, otp_6057, src1]),
+ ?line ok = file:set_cwd(Src1),
+
+ %% Ensure there are no beam files already
+ Ebin = filename:join([PrivDir, otp_6057, ebin]),
+ Test1 = filename:join(Ebin, "test1"),
+ Test2 = filename:join(Ebin, "test2"),
+ ?line ok = ensure_removed([Test1++".beam",Test2++".beam"]),
+
+ %% Call make:files/1
+ ?line up_to_date = make:files([test1, test2]),
+
+ %% Ensure that the beam files are created in the ebin directory
+ Ebin = filename:join([PrivDir, otp_6057, ebin]),
+ case ensure_exists([Test1, Test2]) of
+ ok -> ok;
+ Missing ->
+ ?line ?t:fail({"missing beam file", Missing})
+ end,
+
+ %% Remove the beam files
+ ?line ok = ensure_removed([Test1++".beam", Test2++".beam"]),
+
+ %% Return to original CWD
+ ?line ok = file:set_cwd(CWD),
+
+ ?line ensure_no_messages(),
+ ok.
+
+otp_6057_end(Config) when is_list(Config) ->
+ Config.
+
+ensure_removed([File|Files]) ->
+ file:delete(File),
+ ensure_removed(Files);
+ensure_removed([]) ->
+ ok.
+
+ensure_no_messages() ->
+ ensure_no_messages(0).
+
+ensure_no_messages(N) ->
+ receive
+ Any ->
+ io:format("Unexpected message: ~p", [Any]),
+ ensure_no_messages(N+1)
+ after 0 ->
+ case N of
+ 0 -> ok;
+ N -> ?t:fail()
+ end
+ end.
+
diff --git a/lib/tools/test/make_SUITE_data/Emakefile b/lib/tools/test/make_SUITE_data/Emakefile
new file mode 100644
index 0000000000..ae9abb3cbe
--- /dev/null
+++ b/lib/tools/test/make_SUITE_data/Emakefile
@@ -0,0 +1,20 @@
+%% ``The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved via the world wide web at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
+%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
+%% AB. All Rights Reserved.''
+%%
+%% $Id$
+%%
+
+{test5,['S']}.
+'*'.
diff --git a/lib/tools/test/make_SUITE_data/test1.erl b/lib/tools/test/make_SUITE_data/test1.erl
new file mode 100644
index 0000000000..f4a133008e
--- /dev/null
+++ b/lib/tools/test/make_SUITE_data/test1.erl
@@ -0,0 +1,10 @@
+-module(test1).
+-copyright('Copyright (c) 1991-97 Ericsson Telecom AB').
+-vsn('$Revision: /main/release/2 $').
+-compile(export_all).
+
+f1() ->
+ true.
+
+f2() ->
+ true.
diff --git a/lib/tools/test/make_SUITE_data/test2.erl b/lib/tools/test/make_SUITE_data/test2.erl
new file mode 100644
index 0000000000..5845357c3e
--- /dev/null
+++ b/lib/tools/test/make_SUITE_data/test2.erl
@@ -0,0 +1,10 @@
+-module(test2).
+-copyright('Copyright (c) 1991-97 Ericsson Telecom AB').
+-vsn('$Revision: /main/release/2 $').
+-compile(export_all).
+
+f1() ->
+ true.
+
+f2() ->
+ true.
diff --git a/lib/tools/test/make_SUITE_data/test3.erl b/lib/tools/test/make_SUITE_data/test3.erl
new file mode 100644
index 0000000000..4339260ecb
--- /dev/null
+++ b/lib/tools/test/make_SUITE_data/test3.erl
@@ -0,0 +1,10 @@
+-module(test3).
+-copyright('Copyright (c) 1991-97 Ericsson Telecom AB').
+-vsn('$Revision: /main/release/2 $').
+-compile(export_all).
+
+f1() ->
+ true.
+
+f2() ->
+ true.
diff --git a/lib/tools/test/make_SUITE_data/test4.erl b/lib/tools/test/make_SUITE_data/test4.erl
new file mode 100644
index 0000000000..11b37123f1
--- /dev/null
+++ b/lib/tools/test/make_SUITE_data/test4.erl
@@ -0,0 +1,10 @@
+-module(test4).
+-copyright('Copyright (c) 1991-97 Ericsson Telecom AB').
+-vsn('$Revision: /main/release/2 $').
+-compile(export_all).
+
+f1() ->
+ true.
+
+f2() ->
+ true.
diff --git a/lib/tools/test/make_SUITE_data/test5.erl b/lib/tools/test/make_SUITE_data/test5.erl
new file mode 100644
index 0000000000..108ab8e494
--- /dev/null
+++ b/lib/tools/test/make_SUITE_data/test5.erl
@@ -0,0 +1,10 @@
+-module(test5).
+-copyright('Copyright (c) 1991-97 Ericsson Telecom AB').
+-vsn('$Revision: /main/release/1').
+-compile(export_all).
+
+f1() ->
+ true.
+
+f2() ->
+ true.
diff --git a/lib/tools/test/tools.spec b/lib/tools/test/tools.spec
new file mode 100644
index 0000000000..93d5930472
--- /dev/null
+++ b/lib/tools/test/tools.spec
@@ -0,0 +1 @@
+{topcase, {dir, "../tools_test"}}.
diff --git a/lib/tools/test/tools.spec.win b/lib/tools/test/tools.spec.win
new file mode 100644
index 0000000000..b43d542ff1
--- /dev/null
+++ b/lib/tools/test/tools.spec.win
@@ -0,0 +1,2 @@
+{topcase, {dir, "../tools_test"}}.
+{skip, {emem_SUITE, "Not on windows, yet. FIXME!!!"}}.
diff --git a/lib/tools/test/tools_SUITE.erl b/lib/tools/test/tools_SUITE.erl
new file mode 100644
index 0000000000..6b952f10ab
--- /dev/null
+++ b/lib/tools/test/tools_SUITE.erl
@@ -0,0 +1,56 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-2010. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(tools_SUITE).
+
+-include("test_server.hrl").
+
+%% Default timetrap timeout (set in init_per_testcase).
+-define(default_timeout, ?t:minutes(1)).
+-define(application, tools).
+
+%% Test server specific exports
+-export([all/1]).
+-export([init_per_testcase/2, fin_per_testcase/2]).
+
+%% Test cases must be exported.
+-export([app_test/1]).
+
+all(doc) ->
+ [];
+all(suite) ->
+ [app_test].
+
+init_per_testcase(_Case, Config) ->
+ ?line Dog=test_server:timetrap(?default_timeout),
+ [{watchdog, Dog}|Config].
+fin_per_testcase(_Case, Config) ->
+ Dog=?config(watchdog, Config),
+ test_server:timetrap_cancel(Dog),
+ ok.
+
+%%%
+%%% Test cases starts here.
+%%%
+
+app_test(doc) ->
+ ["Test that the .app file does not contain any `basic' errors"];
+app_test(suite) ->
+ [];
+app_test(Config) when is_list(Config) ->
+ ?line ?t:app_test(tools, tolerant).
diff --git a/lib/tools/test/xref_SUITE.erl b/lib/tools/test/xref_SUITE.erl
new file mode 100644
index 0000000000..0bbb3ba0f1
--- /dev/null
+++ b/lib/tools/test/xref_SUITE.erl
@@ -0,0 +1,2743 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2000-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+
+-module(xref_SUITE).
+
+%-define(debug, true).
+
+-ifdef(debug).
+-define(format(S, A), io:format(S, A)).
+-define(line, put(line, ?LINE), ).
+-define(config(X,Y), "./log_dir/").
+-define(t,test_server).
+-define(datadir, "xref_SUITE_data").
+-define(privdir, "xref_SUITE_priv").
+-define(copydir, "xref_SUITE_priv/datacopy").
+-else.
+-include("test_server.hrl").
+-define(format(S, A), ok).
+-define(datadir, ?config(data_dir, Conf)).
+-define(privdir, ?config(priv_dir, Conf)).
+-define(copydir, ?config(copy_dir, Conf)).
+-endif.
+
+-export([all/1, init/1, fini/1]).
+
+-export([xref/1,
+ addrem/1, convert/1, intergraph/1, lines/1, loops/1,
+ no_data/1, modules/1]).
+
+-export([files/1,
+ add/1, default/1, info/1, lib/1, read/1, read2/1, remove/1,
+ replace/1, update/1, deprecated/1, trycatch/1,
+ abstract_modules/1, fun_mfa/1, qlc/1]).
+
+-export([analyses/1,
+ analyze/1, basic/1, md/1, q/1, variables/1, unused_locals/1]).
+
+-export([misc/1,
+ format_error/1, otp_7423/1, otp_7831/1]).
+
+-import(lists, [append/2, flatten/1, keysearch/3, member/2, sort/1, usort/1]).
+
+-import(sofs, [converse/1, from_term/1, intersection/2, is_sofs_set/1,
+ range/1, relation_to_family/1, set/1, to_external/1,
+ union/2]).
+
+-export([init_per_testcase/2, fin_per_testcase/2]).
+
+%% Checks some info counters of a server and some relations that should hold.
+-export([check_count/1, check_state/1]).
+
+-include_lib("kernel/include/file.hrl").
+
+-include_lib("tools/src/xref.hrl").
+
+all(suite) ->
+ {conf, init, [xref, files, analyses, misc], fini}.
+
+init(Conf) when is_list(Conf) ->
+ DataDir = ?datadir,
+ PrivDir = ?privdir,
+ ?line CopyDir = fname(PrivDir, "datacopy"),
+ ?line TarFile = fname(PrivDir, "datacopy.tgz"),
+ ?line {ok, Tar} = erl_tar:open(TarFile, [write, compressed]),
+ ?line ok = erl_tar:add(Tar, DataDir, CopyDir, [compressed]),
+ ?line ok = erl_tar:close(Tar),
+ ?line ok = erl_tar:extract(TarFile, [compressed]),
+ ?line ok = file:delete(TarFile),
+ [{copy_dir, CopyDir} | Conf].
+
+fini(Conf) when is_list(Conf) ->
+ %% Nothing.
+ Conf.
+
+init_per_testcase(_Case, Config) ->
+ Dog=?t:timetrap(?t:minutes(2)),
+ [{watchdog, Dog}|Config].
+
+fin_per_testcase(_Case, _Config) ->
+ Dog=?config(watchdog, _Config),
+ test_server:timetrap_cancel(Dog),
+ ok.
+
+xref(suite) ->
+ [addrem, convert, intergraph, lines, loops, no_data, modules].
+
+%% Seems a bit short...
+addrem(suite) -> [];
+addrem(doc) -> ["Simple test of removing modules"];
+addrem(Conf) when is_list(Conf) ->
+ S0 = new(),
+
+ F1 = {m1,f1,1},
+ F2 = {m2,f1,2},
+
+ E1 = {F1,F2},
+ E2 = {F2,F1},
+
+ D1 = {F1,12},
+ DefAt_m1 = [D1],
+ X_m1 = [F1],
+ % L_m1 = [],
+ XC_m1 = [E1],
+ LC_m1 = [],
+ LCallAt_m1 = [],
+ XCallAt_m1 = [{E1,13}],
+ Info1 = #xref_mod{name = m1, app_name = [a1]},
+ ?line S1 = add_module(S0, Info1, DefAt_m1, X_m1, LCallAt_m1, XCallAt_m1,
+ XC_m1, LC_m1),
+
+ D2 = {F2,7},
+ DefAt_m2 = [D2],
+ X_m2 = [F2],
+ % L_m2 = [],
+ XC_m2 = [E2],
+ LC_m2 = [],
+ LCallAt_m2 = [],
+ XCallAt_m2 = [{E2,96}],
+ Info2 = #xref_mod{name = m2, app_name = [a2]},
+ ?line S2 = add_module(S1, Info2, DefAt_m2, X_m2, LCallAt_m2, XCallAt_m2,
+ XC_m2, LC_m2),
+
+ ?line S5 = set_up(S2),
+
+ ?line {ok, XMod1, S6} = remove_module(S5, m1),
+ ?line [a1] = XMod1#xref_mod.app_name,
+ ?line {ok, XMod2, S6a} = remove_module(S6, m2),
+ ?line [a2] = XMod2#xref_mod.app_name,
+ ?line S7 = set_up(S6a),
+
+ ?line AppInfo1 = #xref_app{name = a1, rel_name = [r1]},
+ ?line S9 = add_application(S7, AppInfo1),
+ ?line S10 = set_up(S9),
+ ?line AppInfo2 = #xref_app{name = a2, rel_name = [r1]},
+ ?line _S11 = add_application(S10, AppInfo2),
+ ok.
+
+convert(suite) -> [];
+convert(doc) -> ["Coercion of data"];
+convert(Conf) when is_list(Conf) ->
+ S0 = new(),
+
+ F1 = {m1,f1,1},
+ F6 = {m1,f2,6}, % X
+ F2 = {m2,f1,2},
+ F3 = {m2,f2,3}, % X
+ F7 = {m2,f3,7}, % X
+ F4 = {m3,f1,4}, % X
+ F5 = {m3,f2,5},
+
+ UF1 = {m1,f12,17},
+ UF2 = {m17,f17,177},
+
+ E1 = {F1,F3}, % X
+ E2 = {F6,F7}, % X
+ E3 = {F2,F6}, % X
+ E4 = {F1,F4}, % X
+ E5 = {F4,F5},
+ E6 = {F7,F4}, % X
+
+ UE1 = {F2,UF2}, % X
+ UE2 = {F5,UF1}, % X
+
+ D1 = {F1,12},
+ D6 = {F6,3},
+ DefAt_m1 = [D1,D6],
+ X_m1 = [F6],
+ % L_m1 = [F1],
+ XC_m1 = [E1,E2,E4],
+ LC_m1 = [],
+ LCallAt_m1 = [],
+ XCallAt_m1 = [{E1,13},{E2,17},{E4,7}],
+ Info1 = #xref_mod{name = m1, app_name = [a1]},
+ ?line S1 = add_module(S0, Info1, DefAt_m1, X_m1, LCallAt_m1, XCallAt_m1,
+ XC_m1, LC_m1),
+
+ D2 = {F2,7},
+ D3 = {F3,9},
+ D7 = {F7,19},
+ DefAt_m2 = [D2,D3,D7],
+ X_m2 = [F3,F7],
+ % L_m2 = [F2],
+ XC_m2 = [E3,E6,UE1],
+ LC_m2 = [],
+ LCallAt_m2 = [],
+ XCallAt_m2 = [{E3,96},{E6,12},{UE1,77}],
+ Info2 = #xref_mod{name = m2, app_name = [a2]},
+ ?line S2 = add_module(S1, Info2, DefAt_m2, X_m2, LCallAt_m2, XCallAt_m2,
+ XC_m2, LC_m2),
+
+ D4 = {F4,6},
+ D5 = {F5,97},
+ DefAt_m3 = [D4,D5],
+ X_m3 = [F4],
+ % L_m3 = [F5],
+ XC_m3 = [UE2],
+ LC_m3 = [E5],
+ LCallAt_m3 = [{E5,19}],
+ XCallAt_m3 = [{UE2,22}],
+ Info3 = #xref_mod{name = m3, app_name = [a3]},
+ ?line S3 = add_module(S2, Info3, DefAt_m3, X_m3, LCallAt_m3, XCallAt_m3,
+ XC_m3, LC_m3),
+
+ Info4 = #xref_mod{name = m4, app_name = [a2]},
+ ?line S4 = add_module(S3, Info4, [], [], [], [], [], []),
+
+ AppInfo1 = #xref_app{name = a1, rel_name = [r1]},
+ ?line S9 = add_application(S4, AppInfo1),
+ AppInfo2 = #xref_app{name = a2, rel_name = [r1]},
+ ?line S10 = add_application(S9, AppInfo2),
+ AppInfo3 = #xref_app{name = a3, rel_name = [r2]},
+ ?line S11 = add_application(S10, AppInfo3),
+
+ RelInfo1 = #xref_rel{name = r1},
+ ?line S12 = add_release(S11, RelInfo1),
+ RelInfo2 = #xref_rel{name = r2},
+ ?line S13 = add_release(S12, RelInfo2),
+
+ ?line S = set_up(S13),
+
+ ?line {ok, _} = eval("(Lin)(m1->m1:Mod) * m1->m1", type_error, S),
+ ?line {ok, _} = eval("(XXL)(Lin)(m1->m1:Mod) * m1->m1", type_error, S),
+
+ ?line AllDefAt = eval("(Lin) M", S),
+ ?line AllV = eval("(Fun) M", S),
+ ?line AllCallAt = eval("(XXL)(Lin) E", S),
+ ?line AllE = eval("E", S),
+
+ ?line AM = eval("AM", S),
+ ?line A = eval("A", S),
+ ?line R = eval("R", S),
+
+
+ % vertices
+ % general 1 step
+ ?line {ok, _} = eval("(Fun) (Lin) M", AllV, S),
+ ?line {ok, _} = eval("(Fun) (Lin) (Lin) M", AllV, S),
+ ?line {ok, _} = eval(f("(Fun) (Lin) ~p", [[F1, F3]]), [F1,F3], S),
+ ?line {ok, _} = eval(f("(Mod) ~p", [AllV]), [m1,m17,m2,m3], S),
+ ?line {ok, _} = eval(f("(Mod) ~p", [[F1,F3,F6]]), [m1,m2], S),
+ ?line {ok, _} = eval("(App) M", A, S),
+ ?line {ok, _} = eval(f("(App) ~p", [[m1,m2,m4]]), [a1,a2], S),
+ ?line {ok, _} = eval(f("(Rel) ~p", [A]), R, S),
+ ?line {ok, _} = eval(f("(Rel) ~p", [[a1,a2,a2]]), [r1], S),
+ % general 2 steps
+ ?line {ok, _} = eval("(Mod) (Lin) M", [m1,m17,m2,m3], S),
+ ?line {ok, _} = eval(f("(App) ~p", [AllV]), [a1,a2,a3], S),
+ ?line {ok, _} = eval("(Rel) M", R, S),
+ % general 4 steps
+ ?line {ok, _} = eval("(Rel) (Lin) M", [r1,r2], S),
+
+ % special 1 step
+ ?line {ok, _} = eval(f("(Lin) ~p", [AllV]), AllDefAt, S),
+ ?line {ok, _} = eval(f("(Lin) ~p", [[F1,F3]]), [{F1,12},{F3,9}], S),
+ ?line {ok, _} = eval("(Fun) M", AllV, S),
+ ?line {ok, _} = eval(f("(Fun) ~p", [[m1,m2]]), [F1,F2,F3,F6,F7,UF1], S),
+ ?line {ok, _} = eval(f("(Mod) ~p", [A]), AM, S),
+ ?line {ok, _} = eval(f("(Mod) ~p", [[a1,a2]]), [m1,m2,m4], S),
+ ?line {ok, _} = eval(f("(App) ~p", [R]), A, S),
+ ?line {ok, _} = eval(f("(App) ~p", [[r1]]), [a1,a2], S),
+ % special 2 steps
+ ?line {ok, _} = eval("(Lin) M", AllDefAt, S),
+ ?line AnalyzedV = eval("(Fun) AM", S),
+ ?line {ok, _} = eval(f("(Fun) ~p", [A]), AnalyzedV, S),
+ ?line {ok, _} = eval(f("(Mod) ~p", [R]), AM, S),
+ % special 4 steps
+ ?line AnalyzedAllDefAt = eval("(Lin) AM", S),
+ ?line {ok, _} = eval("(Lin) R", AnalyzedAllDefAt, S),
+
+ % edges
+ Ms = [{m1,m2},{m1,m3},{m2,m1},{m2,m3},{m3,m3}],
+ UMs = [{m2,m17},{m3,m1}],
+ AllMs = append(Ms, UMs),
+ As = [{a1,a2},{a1,a3},{a2,a1},{a2,a3},{a3,a3}],
+ Rs = [{r1,r1},{r1,r2},{r2,r2}],
+
+ % general 1 step
+ ?line {ok, _} = eval("(Fun) (Lin) E", AllE, S),
+ ?line {ok, _} = eval(f("(Fun)(Lin) ~p", [[E1, E6]]), [E1, E6], S),
+ ?line {ok, _} = eval("(Mod) E", AllMs, S),
+ ?line {ok, _} = eval(f("(Mod) ~p", [[E1, E6]]), [{m1,m2},{m2,m3}], S),
+ ?line {ok, _} = eval(f("(App) ~p", [As]), As, S),
+ ?line {ok, _} = eval("(App) [m1->m2,m2->m3]", [{a1,a2},{a2,a3}], S),
+ ?line {ok, _} = eval(f("(Rel) ~p", [As]), Rs, S),
+ ?line {ok, _} = eval("(Rel) a1->a2", [{r1,r1}], S),
+
+ % special 1 step
+ ?line {ok, _} = eval("(XXL) (Lin) (Fun) E", AllCallAt, S),
+ ?line {ok, _} = eval("(XXL) (XXL) (Lin) (Fun) E", AllCallAt, S),
+
+ ?line {ok, _} = eval(f("(XXL) (Lin) ~p", [[E1, E6]]),
+ [{{D1,D3},[13]}, {{D7,D4},[12]}], S),
+ ?line {ok, _} = eval(f("(Fun) ~p", [AllMs]), AllE, S),
+ ?line {ok, _} = eval("(Fun) [m1->m2,m2->m3]", [E1,E2,E6], S),
+ ?line {ok, _} = eval(f("(Mod) ~p", [As]), Ms, S),
+ ?line {ok, _} = eval("(Mod) [a1->a2,a2->a3]", [{m1,m2},{m2,m3}], S),
+ ?line {ok, _} = eval(f("(App) ~p", [Rs]), As, S),
+ ?line {ok, _} = eval("(App) r1->r1", [{a1,a2},{a2,a1}], S),
+ ok.
+
+intergraph(suite) -> [];
+intergraph(doc) -> ["Inter Call Graph"];
+intergraph(Conf) when is_list(Conf) ->
+ S0 = new(),
+
+ F1 = {m1,f1,1}, % X
+ F2 = {m1,f2,2}, % X
+ F3 = {m1,f3,3},
+ F4 = {m1,f4,4},
+ F5 = {m1,f5,5},
+
+ F6 = {m2,f1,6}, % X
+ F7 = {m2,f1,7},
+ F8 = {m2,f1,8},
+ F9 = {m2,f1,9},
+ F10 = {m2,f1,10},
+ F11 = {m2,f1,11},
+
+ % Note: E1 =:= E4!
+ E1 = {F2,F1},
+ E2 = {F2,F3},
+ E3 = {F3,F1},
+ E4 = {F2,F1}, % X
+ E5 = {F4,F2},
+ E6 = {F5,F4},
+ E7 = {F4,F5},
+
+ E8 = {F6,F7},
+ E9 = {F7,F8},
+ E10 = {F8,F1}, % X
+ E11 = {F6,F9},
+ E12 = {F6,F10},
+ E13 = {F9,F11},
+ E14 = {F10,F11},
+ E15 = {F11,F1}, % X
+
+ D1 = {F1,1},
+ D2 = {F2,2},
+ D3 = {F3,3},
+ D4 = {F4,4},
+ D5 = {F5,5},
+ DefAt_m1 = [D1,D2,D3,D4,D5],
+ X_m1 = [F1,F2],
+ % L_m1 = [F3,F4,F5],
+ XC_m1 = [E4],
+ LC_m1 = [E1,E2,E3,E5,E6,E7],
+ % Note: E1 and E4 together!
+ LCallAt_m1 = [{E1,1},{E2,2},{E3,3},{E5,5},{E6,6},{E7,7}],
+ XCallAt_m1 = [{E1,4}],
+ Info1 = #xref_mod{name = m1, app_name = [a1]},
+ ?line S1 = add_module(S0, Info1, DefAt_m1, X_m1, LCallAt_m1, XCallAt_m1,
+ XC_m1, LC_m1),
+
+ D6 = {F6,6},
+ D7 = {F7,7},
+ D8 = {F8,8},
+ D9 = {F9,9},
+ D10 = {F10,10},
+ D11 = {F11,11},
+ DefAt_m2 = [D6,D7,D8,D9,D10,D11],
+ X_m2 = [F6],
+ % L_m2 = [F7,F8,F9,F10,F11],
+ XC_m2 = [E10,E15],
+ LC_m2 = [E8,E9,E11,E12,E13,E14],
+ LCallAt_m2 = [{E8,8},{E9,9},{E11,11},{E12,12},{E13,13},{E14,14}],
+ XCallAt_m2 = [{E10,10},{E15,15}],
+ Info2 = #xref_mod{name = m2, app_name = [a2]},
+ ?line S2 = add_module(S1, Info2, DefAt_m2, X_m2, LCallAt_m2, XCallAt_m2,
+ XC_m2, LC_m2),
+
+ AppInfo1 = #xref_app{name = a1, rel_name = [r1]},
+ ?line S5 = add_application(S2, AppInfo1),
+ AppInfo2 = #xref_app{name = a2, rel_name = [r1]},
+ ?line S6 = add_application(S5, AppInfo2),
+
+ RelInfo = #xref_rel{name = r1},
+ ?line S7 = add_release(S6, RelInfo),
+
+ ?line S = set_up(S7),
+
+ ?line {ok, _} = eval("EE | m1", [E1,E5,E6,E7], S),
+ ?line {ok, _} = eval("EE | m2", [{F6,F1}], S),
+ ?line {ok, _} = eval("EE | m2 + EE | m2", [{F6,F1}], S),
+
+ ?line {ok, _} = eval("(Fun)(Lin)(E | m1)",
+ to_external(union(set(XC_m1), set(LC_m1))), S),
+ ?line {ok, _} = eval("(XXL)(ELin) (EE | m1)",
+ [{{D2,D1},[1,2,4]},{{D4,D2},[5]},{{D5,D4},[6]},{{D4,D5},[7]}],
+ S),
+ ?line {ok, _} = eval("(XXL)(ELin)(EE | m2)", [{{D6,D1},[8,11,12]}], S),
+ ?line {ok, _} = eval("(XXL)(ELin)(ELin)(EE | m2)",
+ [{{D6,D1},[8,11,12]}], S),
+
+ %% Combining graphs (equal or different):
+ ?line {ok, _} = eval("(XXL)(ELin)(EE | m2 + EE | m2)",
+ [{{D6,D1},[8,11,12]}], S),
+ ?line {ok, _} = eval("(XXL)(ELin)(EE | m2 * EE | m2)",
+ [{{D6,D1},[8,11,12]}], S),
+ ?line {ok, _} = eval("(XXL)(ELin)(EE | m2 - EE | m1)",
+ [{{D6,D1},[8,11,12]}], S),
+ ?line {ok, _} = eval("(XXL)(ELin)(EE | m2 - E | m2)",
+ [{{D6,D1},[8,11,12]}], S),
+ ?line {ok, _} = eval("(XXL)(ELin)(Fun)(ELin)(EE | m2)",
+ [{{D6,D1},[8,11,12]}], S),
+ ?line {ok, _} = eval("EE | m1 + E | m1", LC_m1, S),
+ ?line {ok, _} = eval(f("EE | ~p + E | ~p", [F2, F2]), [E1,E2], S),
+ %% [1,4] from 'calls' is a subset of [1,2,4] from Inter Call Graph:
+ ?line {ok, _} = eval(f("(XXL)(Lin) (E | ~p)", [F2]),
+ [{{D2,D1},[1,4]},{{D2,D3},[2]}], S),
+
+ ?line {ok, _} = eval(f("(XXL)(ELin) (EE | ~p)", [F2]),
+ [{{D2,D1},[1,2,4]}], S),
+ ?line {ok, _} = eval(f("(XXL)((ELin)(EE | ~p) + (Lin)(E | ~p))", [F2, F2]),
+ [{{D2,D1},[1,2,4]},{{D2,D3},[2]}], S),
+ ?line {ok, _} =
+ eval(f("(XXL)((ELin) ~p + (Lin) ~p)", [{F2, F1}, {F2, F1}]),
+ [{{D2,D1},[1,2,4]}], S),
+ ?line {ok, _} = eval(f("(Fun)(Lin) ~p", [{F2, F1}]), [E1], S),
+ %% The external call E4 is included in the reply:
+ ?line {ok, _} = eval("(XXL)(Lin)(LC | m1)",
+ [{{D2,D1},[1,4]},{{D2,D3},[2]},{{D3,D1},[3]},
+ {{D4,D2},[5]},{{D4,D5},[7]},{{D5,D4},[6]}], S),
+ %% The local call E1 is included in the reply:
+ ?line {ok, _} = eval("(XXL)(Lin)(XC | m1)", [{{D2,D1},[1,4]}], S),
+
+ ?line {ok, _} = eval(f("(LLin) (E | ~p || ~p) + (XLin) (E | ~p || ~p)",
+ [F2, F1, F2, F1]), [{E4,[1,4]}], S),
+
+ ?line {ok, _} = eval("# (ELin) E", 6, S),
+
+ ok.
+
+lines(suite) -> [];
+lines(doc) -> ["More test of Inter Call Graph, and regular expressions"];
+lines(Conf) when is_list(Conf) ->
+ S0 = new(),
+
+ F1 = {m1,f1,1}, % X
+ F2 = {m1,f2,2},
+ F3 = {m1,f3,3},
+ F4 = {m2,f4,4}, % X
+ F5 = {m1,f5,5}, % X
+ F6 = {m1,f6,6},
+
+ E1 = {F1,F2},
+ E2 = {F2,F1}, % X
+ E3 = {F3,F2},
+ E4 = {F1,F4}, % X
+ E5 = {F2,F4}, % X
+ E6 = {F5,F6},
+ E7 = {F6,F4}, % X
+
+ D1 = {F1,1},
+ D2 = {F2,2},
+ D3 = {F3,3},
+ D4 = {F4,4},
+ D5 = {F5,5},
+ D6 = {F6,6},
+
+ DefAt_m1 = [D1,D2,D3,D5,D6],
+ X_m1 = [F1,F5],
+ % L_m1 = [F2,F3,F6],
+ XC_m1 = [E4,E5,E7],
+ LC_m1 = [E1,E2,E3,E6],
+ LCallAt_m1 = [{E1,1},{E3,3},{E6,6}],
+ XCallAt_m1 = [{E2,2},{E4,4},{E5,5},{E7,7}],
+ Info1 = #xref_mod{name = m1, app_name = [a1]},
+ ?line S1 = add_module(S0, Info1, DefAt_m1, X_m1, LCallAt_m1, XCallAt_m1,
+ XC_m1, LC_m1),
+
+ DefAt_m2 = [D4],
+ X_m2 = [F4],
+ % L_m2 = [],
+ XC_m2 = [],
+ LC_m2 = [],
+ LCallAt_m2 = [],
+ XCallAt_m2 = [],
+ Info2 = #xref_mod{name = m2, app_name = [a2]},
+ ?line S2 = add_module(S1, Info2, DefAt_m2, X_m2, LCallAt_m2, XCallAt_m2,
+ XC_m2, LC_m2),
+
+ AppInfo1 = #xref_app{name = a1, rel_name = [r1]},
+ ?line S5 = add_application(S2, AppInfo1),
+ AppInfo2 = #xref_app{name = a2, rel_name = [r1]},
+ ?line S6 = add_application(S5, AppInfo2),
+
+ RelInfo = #xref_rel{name = r1},
+ ?line S7 = add_release(S6, RelInfo),
+
+ ?line S = set_up(S7),
+
+ ?line {ok, _} = eval("(XXL) (ELin) (EE | m1)",
+ [{{D1,D1},[1]},{{D1,D4},[1,4]},{{D3,D1},[3]},{{D3,D4},[3]},
+ {{D5,D4},[6]}], S),
+ ?line {ok, _} = eval("(XXL)(Lin) (E | m1)",
+ [{{D1,D2},[1]},{{D1,D4},[4]},{{D2,D1},[2]},
+ {{D2,D4},[5]},{{D3,D2},[3]},{{D5,D6},[6]},{{D6,D4},[7]}],
+ S),
+ ?line {ok, _} = eval("(E | m1) + (EE | m1)",
+ [E1,E2,E3,E4,E5,E6,E7,{F1,F1},{F3,F1},{F3,F4},{F5,F4}],
+ S),
+ ?line {ok, _} = eval("(Lin)(E | m1)",
+ [{E4,[4]},{E1,[1]},{E2,[2]},{E5,[5]},
+ {E3,[3]},{E7,[7]},{E6,[6]}], S),
+ ?line {ok, _} = eval("(ELin)(EE | m1)",
+ [{{F1,F1},[1]},{{F1,F4},[1,4]},{{F3,F1},[3]},{{F3,F4},[3]},
+ {{F5,F4},[6]}], S),
+ ?line {ok, _} = eval("(Lin)(E | m1) + (ELin)(EE | m1)",
+ [{E4,[1,4]},{E1,[1]},{E2,[2]},{E5,[5]},
+ {E3,[3]},{E7,[7]},{E6,[6]},
+ {{F1,F1},[1]},{{F3,F1},[3]},{{F3,F4},[3]},
+ {{F5,F4},[6]}], S),
+ ?line {ok, _} = eval("(Lin)(E | m1) - (ELin)(EE | m1)",
+ [{E1,[1]},{E2,[2]},{E5,[5]},
+ {E3,[3]},{E7,[7]},{E6,[6]}], S),
+ ?line {ok, _} = eval("(Lin)(E | m1) * (ELin)(EE | m1)",
+ [{E4,[4]}], S),
+ ?line {ok, _} = eval("(XXL)(Lin) (E | m1)",
+ [{{D1,D4},[4]},{{D1,D2},[1]},{{D2,D1},[2]},{{D2,D4},[5]},
+ {{D3,D2},[3]},{{D6,D4},[7]},{{D5,D6},[6]}], S),
+ ?line {ok, _} = eval("(XXL)(ELin) (EE | m1)",
+ [{{D1,D1},[1]},{{D1,D4},[1,4]},{{D3,D1},[3]},{{D3,D4},[3]},
+ {{D5,D4},[6]}], S),
+ ?line {ok, _} = eval("(XXL)(Lin)(Fun)(Lin) (E | m1)",
+ [{{D1,D4},[4]},{{D1,D2},[1]},{{D2,D1},[2]},{{D2,D4},[5]},
+ {{D3,D2},[3]},{{D6,D4},[7]},{{D5,D6},[6]}], S),
+ ?line {ok, _} = eval("(XXL)(ELin)(Fun)(ELin) (EE | m1)",
+ [{{D1,D1},[1]},{{D1,D4},[1,4]},{{D3,D1},[3]},{{D3,D4},[3]},
+ {{D5,D4},[6]}], S),
+
+ %% A few tests on regexp.
+ ?line {ok, _} = eval("\"(foo\":Mod", parse_error, S),
+ ?line {ok, _} = eval("_Foo:_/_", parse_error, S),
+ ?line {ok, _} = eval("\".*foo\"", parse_error, S),
+ ?line {ok, _} = eval("_:_/_:Lin", parse_error, S),
+ ?line {ok, _} = eval("_:_/_:Mod", parse_error, S),
+ ?line {ok, _} = eval("_:_/_:App", parse_error, S),
+ ?line {ok, _} = eval("_:_/_:Rel", parse_error, S),
+ ?line {ok, _} = eval("m2:_/4", [F4], S),
+ ?line {ok, _} = eval("m2:_/4:Fun", [F4], S),
+ ?line {ok, _} = eval("\"m.?\":\"f.*\"/\"6\"", [F6], S),
+ ?line {ok, _} = eval("_:_/6", [F6], S),
+ ?line {ok, _} = eval("m1:\"f1\"/_", [F1], S),
+ ?line {ok, _} = eval("\"m1\":f1/_", [F1], S),
+ ?line {ok, _} = eval("\"m1\":Mod", [m1], S),
+ ?line {ok, _} = eval("\"a1\":App", [a1], S),
+ ?line {ok, _} = eval("\"r1\":Rel", [r1], S),
+ ?line {ok, _} = eval("_:_/-1", [], S),
+
+ ok.
+
+loops(suite) -> [];
+loops(doc) -> ["More Inter Call Graph, loops and \"unusual\" cases"];
+loops(Conf) when is_list(Conf) ->
+ S0 = new(),
+
+ F1 = {m1,f1,1}, % X
+ F2 = {m1,f2,2},
+ F3 = {m1,f3,3}, % X
+ F4 = {m1,f4,4},
+ F5 = {m1,f5,5},
+ F6 = {m1,f1,6}, % X
+ F7 = {m1,f1,7},
+
+ E1 = {F1,F1}, % X
+ E2 = {F2,F2},
+ E3 = {F3,F4},
+ E4 = {F4,F5},
+ E5 = {F5,F3}, % X
+
+ D1 = {F1,1},
+ D2 = {F2,2},
+ D3 = {F3,3},
+ D4 = {F4,4},
+ D5 = {F5,5},
+ D6 = {F6,6},
+ D7 = {F7,7},
+ DefAt_m1 = [D1,D2,D3,D4,D5,D6,D7],
+ X_m1 = [F1,F3,F6],
+ % L_m1 = [F2,F4,F5],
+ XC_m1 = [],
+ LC_m1 = [E1,E2,E3,E4,E5],
+ LCallAt_m1 = [{E2,2},{E3,3},{E4,4}],
+ XCallAt_m1 = [{E1,1},{E5,5}],
+ Info1 = #xref_mod{name = m1, app_name = [a1]},
+ ?line S1 = add_module(S0, Info1, DefAt_m1, X_m1, LCallAt_m1, XCallAt_m1,
+ XC_m1, LC_m1),
+
+ ?line S = set_up(S1),
+
+ % Neither F6 nor F7 is included. Perhaps one should change that?
+ ?line {ok, _} = eval("EE | m1", [E1,E2,{F3,F3}], S),
+ ?line {ok, _} = eval(f("(XXL)(ELin) (EE | ~p)", [F3]), [{{D3,D3},[3]}], S),
+
+ ?line {ok, _} = eval("m1->m1 | m1->m1", type_error, S),
+ ?line {ok, _} = eval(f("~p | ~p", [F2, F1]), type_error, S),
+
+ ?line {ok, _} = eval(f("range (closure EE | ~p)", [F1]), [F1], S),
+ ?line {ok, _} = eval(f("domain (closure EE || ~p)", [F3]), [F3], S),
+
+ ?line {ok, _} = eval(f("domain (closure E || ~p)", [F3]), [F3,F4,F5], S),
+
+ ?line {ok, _} = eval("components E", [[F1],[F2],[F3,F4,F5]], S),
+ ?line {ok, _} = eval("components EE", [[F1],[F2],[F3]], S),
+
+ ok.
+
+no_data(suite) -> [];
+no_data(doc) -> ["Simple tests when there is no data"];
+no_data(Conf) when is_list(Conf) ->
+ S0 = new(),
+ ?line S1 = set_up(S0),
+ ?line {ok, _} = eval("M", [], S1),
+ ?line {ok, _} = eval("A", [], S1),
+ ?line {ok, _} = eval("R", [], S1),
+
+ ModInfo = #xref_mod{name = m, app_name = []},
+ ?line S2 = add_module(S1, ModInfo, [], [], [], [], [], []),
+ AppInfo = #xref_app{name = a, rel_name = []},
+ ?line S3 = add_application(S2, AppInfo),
+ RelInfo = #xref_rel{name = r, dir = ""},
+ ?line S4 = add_release(S3, RelInfo),
+ ?line S5 = set_up(S4),
+ ?line {ok, _} = eval("M", [m], S5),
+ ?line {ok, _} = eval("A", [a], S5),
+ ?line {ok, _} = eval("R", [r], S5),
+ ok.
+
+modules(suite) -> [];
+modules(doc) -> ["Modules mode"];
+modules(Conf) when is_list(Conf) ->
+ CopyDir = ?copydir,
+ Dir = fname(CopyDir, "rel2"),
+ X = fname(Dir, "x.erl"),
+ Y = fname(Dir, "y.erl"),
+ A1_1 = fname([Dir,"lib","app1-1.1"]),
+ A2 = fname([Dir,"lib","app2-1.1"]),
+ EB1_1 = fname(A1_1, "ebin"),
+ EB2 = fname(A2, "ebin"),
+ Xbeam = fname(EB2, "x.beam"),
+ Ybeam = fname(EB1_1, "y.beam"),
+
+ ?line {ok, x} = compile:file(X, [debug_info, {outdir,EB2}]),
+ ?line {ok, y} = compile:file(Y, [debug_info, {outdir,EB1_1}]),
+
+ ?line {ok, S0} = xref_base:new([{xref_mode, modules}]),
+ ?line {ok, release2, S1} =
+ xref_base:add_release(S0, Dir, [{name,release2}]),
+ ?line S = set_up(S1),
+ ?line {{error, _, {unavailable_analysis, undefined_function_calls}}, _} =
+ xref_base:analyze(S, undefined_function_calls),
+ ?line {{error, _, {unavailable_analysis, locals_not_used}}, _} =
+ xref_base:analyze(S, locals_not_used),
+ ?line {{error, _, {unavailable_analysis, {call, foo}}}, _} =
+ xref_base:analyze(S, {call, foo}),
+ ?line {{error, _, {unavailable_analysis, {use, foo}}}, _} =
+ xref_base:analyze(S, {use, foo}),
+ ?line analyze(undefined_functions, [{x,undef,0}], S),
+ ?line 5 = length(xref_base:info(S)),
+
+ %% More: all info, conversions.
+
+ ?line ok = file:delete(Xbeam),
+ ?line ok = file:delete(Ybeam),
+ ?line ok = xref_base:delete(S),
+ ok.
+
+files(suite) ->
+ [add, default, info, lib, read, read2, remove, replace, update,
+ deprecated, trycatch, abstract_modules, fun_mfa, qlc].
+
+add(suite) -> [];
+add(doc) -> ["Add modules, applications, releases, directories"];
+add(Conf) when is_list(Conf) ->
+ CopyDir = ?copydir,
+ Dir = fname(CopyDir, "rel2"),
+ UDir = fname([CopyDir,"dir","unreadable"]),
+ DDir = fname(CopyDir,"dir"),
+ UFile = fname([DDir, "dir","unreadable.beam"]),
+ X = fname(Dir, "x.erl"),
+ Y = fname(Dir, "y.erl"),
+ A1_1 = fname([Dir,"lib","app1-1.1"]),
+ A2 = fname([Dir,"lib","app2-1.1"]),
+ EB1_1 = fname(A1_1, "ebin"),
+ EB2 = fname(A2, "ebin"),
+ Xbeam = fname(EB2, "x.beam"),
+ Ybeam = fname(EB1_1, "y.beam"),
+
+ ?line {ok, x} = compile:file(X, [debug_info, {outdir,EB2}]),
+ ?line {ok, y} = compile:file(Y, [debug_info, {outdir,EB1_1}]),
+
+ ?line case os:type() of
+ {unix, _} ->
+ ?line make_udir(UDir),
+ ?line make_ufile(UFile);
+ _ ->
+ true
+ end,
+
+ ?line {error, _, {invalid_options,[not_an_option] }} =
+ xref_base:new([not_an_option]),
+ ?line {error, _, {invalid_options,[{verbose,not_a_value}] }} =
+ xref_base:new([{verbose,not_a_value}]),
+ ?line S = new(),
+ ?line {error, _, {invalid_options,[not_an_option]}} =
+ xref_base:set_up(S, [not_an_option]),
+ ?line {error, _, {invalid_options,[{builtins,true},not_an_option]}} =
+ xref_base:add_directory(S, foo, [{builtins,true},not_an_option]),
+ ?line {error, _, {invalid_options,[{builtins,not_a_value}]}} =
+ xref_base:add_directory(S, foo, [{builtins,not_a_value}]),
+ ?line {error, _, {invalid_filename,{foo,bar}}} =
+ xref_base:add_directory(S, {foo,bar}, []),
+ ?line {error, _, {invalid_options,[{builtins,true},not_an_option]}} =
+ xref_base:add_module(S, foo, [{builtins,true},not_an_option]),
+ ?line {error, _, {invalid_options,[{builtins,not_a_value}]}} =
+ xref_base:add_module(S, foo, [{builtins,not_a_value}]),
+ ?line {error, _, {invalid_filename,{foo,bar}}} =
+ xref_base:add_module(S, {foo,bar}, []),
+ ?line {error, _, {invalid_options,[{builtins,true},not_an_option]}} =
+ xref_base:add_application(S, foo, [{builtins,true},not_an_option]),
+ ?line {error, _, {invalid_options,[{builtins,not_a_value}]}} =
+ xref_base:add_application(S, foo, [{builtins,not_a_value}]),
+ ?line {error, _, {invalid_filename,{foo,bar}}} =
+ xref_base:add_application(S, {foo,bar}, []),
+ ?line {error, _, {invalid_options,[not_an_option]}} =
+ xref_base:add_release(S, foo, [not_an_option]),
+ ?line {error, _, {invalid_options,[{builtins,not_a_value}]}} =
+ xref_base:add_release(S, foo, [{builtins,not_a_value}]),
+ ?line {error, _, {invalid_filename,{foo,bar}}} =
+ xref_base:add_release(S, {foo,bar}, []),
+ ?line {ok, S1} =
+ xref_base:set_default(S, [{verbose,false}, {warnings, false}]),
+ ?line case os:type() of
+ {unix, _} ->
+ ?line {error, _, {file_error, _, _}} =
+ xref_base:add_release(S, UDir);
+ _ ->
+ true
+ end,
+ ?line {error, _, {file_error, _, _}} =
+ xref_base:add_release(S, fname(["/a/b/c/d/e/f","__foo"])),
+ ?line {ok, release2, S2} =
+ xref_base:add_release(S1, Dir, [{name,release2}]),
+ ?line {error, _, {module_clash, {x, _, _}}} =
+ xref_base:add_module(S2, Xbeam),
+ ?line {ok, S3} = xref_base:remove_release(S2, release2),
+ ?line {ok, rel2, S4} = xref_base:add_release(S3, Dir),
+ ?line {error, _, {release_clash, {rel2, _, _}}} =
+ xref_base:add_release(S4, Dir),
+ ?line {ok, S5} = xref_base:remove_release(S4, rel2),
+ %% One unreadable file and one JAM file found (no verification here):
+ ?line {ok, [], S6} = xref_base:add_directory(S5, fname(CopyDir,"dir"),
+ [{recurse,true}, {warnings,true}]),
+ ?line case os:type() of
+ {unix, _} ->
+ ?line {error, _, {file_error, _, _}} =
+ xref_base:add_directory(S6, UDir);
+ _ ->
+ true
+ end,
+ ?line {ok, app1, S7} = xref_base:add_application(S6, A1_1),
+ ?line {error, _, {application_clash, {app1, _, _}}} =
+ xref_base:add_application(S7, A1_1),
+ ?line {ok, S8} = xref_base:remove_application(S7, app1),
+ ?line ok = xref_base:delete(S8),
+ ?line ok = file:delete(Xbeam),
+ ?line ok = file:delete(Ybeam),
+ ?line case os:type() of
+ {unix, _} ->
+ ?line ok = file:del_dir(UDir),
+ ?line ok = file:delete(UFile);
+ _ ->
+ true
+ end,
+ ok.
+
+default(suite) -> [];
+default(doc) -> ["Default values of options"];
+default(Conf) when is_list(Conf) ->
+ S = new(),
+ ?line {error, _, {invalid_options,[not_an_option]}} =
+ xref_base:set_default(S, not_an_option, true),
+ ?line {error, _, {invalid_options,[{builtins, not_a_value}]}} =
+ xref_base:set_default(S, builtins, not_a_value),
+ ?line {error, _, {invalid_options,[not_an_option]}} =
+ xref_base:get_default(S, not_an_option),
+ ?line {error, _, {invalid_options,[not_an_option]}} =
+ xref_base:set_default(S, [not_an_option]),
+
+ ?line D = xref_base:get_default(S),
+ ?line [{builtins,false},{recurse,false},{verbose,false},{warnings,true}] =
+ D,
+
+ ?line ok = xref_base:delete(S),
+ ok.
+
+info(suite) -> [];
+info(doc) -> ["The info functions"];
+info(Conf) when is_list(Conf) ->
+ CopyDir = ?copydir,
+ Dir = fname(CopyDir,"rel2"),
+ LDir = fname(CopyDir,"lib_test"),
+ X = fname(Dir, "x.erl"),
+ Y = fname(Dir, "y.erl"),
+ A1_1 = fname([Dir,"lib","app1-1.1"]),
+ A2 = fname([Dir,"lib","app2-1.1"]),
+ EB1_1 = fname(A1_1, "ebin"),
+ EB2 = fname(A2, "ebin"),
+ Xbeam = fname(EB2, "x.beam"),
+ Ybeam = fname(EB1_1, "y.beam"),
+
+ ?line {ok, x} = compile:file(X, [debug_info, {outdir,EB2}]),
+ ?line {ok, y} = compile:file(Y, [debug_info, {outdir,EB1_1}]),
+
+ ?line {ok, _} = start(s),
+ ?line {error, _, {no_such_info, release}} = xref:info(s, release),
+ ?line {error, _, {no_such_info, release}} = xref:info(s, release, rel),
+ ?line {error, _, {no_such_module, mod}} = xref:info(s, modules, mod),
+ ?line {error, _, {no_such_application, app}} =
+ xref:info(s, applications, app),
+ ?line {error, _, {no_such_release, rel}} = xref:info(s, releases, rel),
+ ?line ok = xref:set_default(s, [{verbose,false}, {warnings, false}]),
+ ?line {ok, rel2} = xref:add_release(s, Dir),
+ ?line 9 = length(xref:info(s)),
+ ?line [{x,_}, {y, _}] = xref:info(s, modules),
+ ?line [{app1,_}, {app2, _}] = xref:info(s, applications),
+ ?line [{rel2,_}] = xref:info(s, releases),
+ ?line [] = xref:info(s, libraries),
+ ?line [{x,_}] = xref:info(s, modules, x),
+ ?line [{rel2,_}] = xref:info(s, releases, rel2),
+ ?line {error, _, {no_such_library, foo}} = xref:info(s, libraries, [foo]),
+
+ ?line {ok, lib1} =
+ compile:file(fname(LDir,lib1),[debug_info,{outdir,LDir}]),
+ ?line {ok, lib2} =
+ compile:file(fname(LDir,lib2),[debug_info,{outdir,LDir}]),
+ ?line ok = xref:set_library_path(s, [LDir], [{verbose,false}]),
+ ?line [{lib1,_}, {lib2, _}] = xref:info(s, libraries),
+ ?line [{lib1,_}, {lib2, _}] = xref:info(s, libraries, [lib1,lib2]),
+ ?line ok = file:delete(fname(LDir, "lib1.beam")),
+ ?line ok = file:delete(fname(LDir, "lib2.beam")),
+
+ ?line check_state(s),
+
+ ?line xref:stop(s),
+
+ ?line ok = file:delete(Xbeam),
+ ?line ok = file:delete(Ybeam),
+
+ ok.
+
+lib(suite) -> [];
+lib(doc) -> ["Library modules"];
+lib(Conf) when is_list(Conf) ->
+ CopyDir = ?copydir,
+ Dir = fname(CopyDir,"lib_test"),
+ UDir = fname([CopyDir,"dir","non_existent"]),
+
+ ?line {ok, lib1} = compile:file(fname(Dir,lib1),[debug_info,{outdir,Dir}]),
+ ?line {ok, lib2} = compile:file(fname(Dir,lib2),[debug_info,{outdir,Dir}]),
+ ?line {ok, lib3} = compile:file(fname(Dir,lib3),[debug_info,{outdir,Dir}]),
+ ?line {ok, t} = compile:file(fname(Dir,t),[debug_info,{outdir,Dir}]),
+
+ ?line {ok, _} = start(s),
+ ?line ok = xref:set_default(s, [{verbose,false}, {warnings, false}]),
+ ?line {ok, t} = xref:add_module(s, fname(Dir,"t.beam")),
+ ?line {error, _, {invalid_options,[not_an_option]}} =
+ xref:set_library_path(s, ["foo"], [not_an_option]),
+ ?line {error, _, {invalid_path,otp}} = xref:set_library_path(s,otp),
+ ?line {error, _, {invalid_path,[""]}} = xref:set_library_path(s,[""]),
+ ?line {error, _, {invalid_path,[[$a | $b]]}} =
+ xref:set_library_path(s,[[$a | $b]]),
+ ?line {error, _, {invalid_path,[otp]}} = xref:set_library_path(s,[otp]),
+ ?line {ok, []} = xref:get_library_path(s),
+ ?line ok = xref:set_library_path(s, [Dir], [{verbose,false}]),
+ ?line {ok, UnknownFunctions} = xref:q(s, "U"),
+ ?line [{lib1,unknown,0}, {lib2,local,0},
+ {lib2,unknown,0}, {unknown,unknown,0}]
+ = UnknownFunctions,
+ ?line {ok, [{lib2,f,0},{lib3,f,0}]} = xref:q(s, "DF"),
+ ?line {ok, []} = xref:q(s, "DF_1"),
+ ?line {ok, [{lib2,f,0}]} = xref:q(s, "DF_2"),
+ ?line {ok, [{lib2,f,0}]} = xref:q(s, "DF_3"),
+
+ ?line {ok, [unknown]} = xref:q(s, "UM"),
+ ?line {ok, UnknownDefAt} = xref:q(s, "(Lin)U"),
+ ?line [{{lib1,unknown,0},0},{{lib2,local,0},0}, {{lib2,unknown,0},0},
+ {{unknown,unknown,0},0}] = UnknownDefAt,
+ ?line {ok, LibFuns} = xref:q(s, "X * LM"),
+ ?line [{lib2,f,0},{lib3,f,0}] = LibFuns,
+ ?line {ok, LibMods} = xref:q(s, "LM"),
+ ?line [lib1,lib2,lib3] = LibMods,
+ ?line {ok, [{{lib2,f,0},0},{{lib3,f,0},0}]} = xref:q(s, "(Lin) (LM * X)"),
+ ?line {ok, [{{lib1,unknown,0},0}, {{lib2,f,0},0}, {{lib2,local,0},0},
+ {{lib2,unknown,0},0}, {{lib3,f,0},0}]} = xref:q(s,"(Lin)LM"),
+ ?line {ok,[lib1,lib2,lib3,t,unknown]} = xref:q(s,"M"),
+ ?line {ok,[{lib2,f,0},{lib3,f,0},{t,t,0}]} = xref:q(s,"X * M"),
+ ?line check_state(s),
+
+ ?line copy_file(fname(Dir, "lib1.erl"), fname(Dir,"lib1.beam")),
+ ?line ok = xref:set_library_path(s, [Dir]),
+ ?line {error, _, _} = xref:q(s, "U"),
+
+ %% OTP-3921. AM and LM not always disjoint.
+ ?line {ok, lib1} = compile:file(fname(Dir,lib1),[debug_info,{outdir,Dir}]),
+ ?line {ok, lib1} = xref:add_module(s, fname(Dir,"lib1.beam")),
+ ?line check_state(s),
+
+ ?line {error, _, {file_error, _, _}} = xref:set_library_path(s, [UDir]),
+
+ ?line xref:stop(s),
+ ?line ok = file:delete(fname(Dir, "lib1.beam")),
+ ?line ok = file:delete(fname(Dir, "lib2.beam")),
+ ?line ok = file:delete(fname(Dir, "lib3.beam")),
+ ?line ok = file:delete(fname(Dir, "t.beam")),
+
+ ?line {ok, cp} = compile:file(fname(Dir,cp),[debug_info,{outdir,Dir}]),
+ ?line {ok, _} = start(s),
+ ?line ok = xref:set_default(s, [{verbose,false}, {warnings, false}]),
+ ?line {ok, cp} = xref:add_module(s, fname(Dir,"cp.beam")),
+ ?line {ok, [{lists, sort, 1}]} = xref:q(s, "U"),
+ ?line ok = xref:set_library_path(s, code_path),
+ ?line {ok, []} = xref:q(s, "U"),
+ ?line check_state(s),
+ ?line xref:stop(s),
+ ?line ok = file:delete(fname(Dir, "cp.beam")),
+ ok.
+
+read(suite) -> [];
+read(doc) -> ["Data read from the Abstract Code"];
+read(Conf) when is_list(Conf) ->
+ CopyDir = ?copydir,
+ Dir = fname(CopyDir,"read"),
+ File = fname(Dir, "read"),
+ Beam = fname(Dir, "read.beam"),
+ ?line {ok, read} = compile:file(File, [debug_info,{outdir,Dir}]),
+ ?line do_read(File, abstract_v2),
+ ?line copy_file(fname(Dir, "read.beam.v1"), Beam),
+ ?line do_read(File, abstract_v1),
+ ?line ok = file:delete(Beam),
+ ok.
+
+do_read(File, Version) ->
+ ?line {ok, _} = start(s),
+ ?line ok = xref:set_default(s, [{verbose,false}, {warnings, false}]),
+ ?line {ok, read} = xref:add_module(s, File),
+
+ ?line {U, OK, OKB} = read_expected(Version),
+
+ %% {ok, UC} = xref:q(s, "(Lin) UC"),
+ %% RR = to_external(converse(family_to_relation(family(UC)))),
+ %% lists:foreach(fun(X) -> io:format("~w~n", [X]) end, RR),
+ Unres = to_external(relation_to_family(converse(from_term(U)))),
+ ?line {ok, Unres} = xref:q(s, "(Lin) UC"),
+
+ %% {ok, EE} = xref:q(s, "(Lin) (E - UC)"),
+ %% AA = to_external(converse(family_to_relation(family(EE)))),
+ %% lists:foreach(fun(X) -> io:format("~w~n", [X]) end, AA),
+ Calls = to_external(relation_to_family(converse(from_term(OK)))),
+ ?line {ok, Calls} = xref:q(s, "(Lin) (E - UC) "),
+
+ ?line ok = check_state(s),
+ ?line {ok, UM} = xref:q(s, "UM"),
+ ?line true = member('$M_EXPR', UM),
+
+ ?line {ok, X} = xref:q(s, "X"),
+ ?line true = member({read, module_info, 0}, X),
+ ?line false = member({foo, module_info, 0}, X),
+ ?line false = member({erlang, module_info, 0}, X),
+ ?line {ok, Unknowns} = xref:q(s, "U"),
+ ?line false = member({read, module_info, 0}, Unknowns),
+ ?line true = member({foo, module_info, 0}, Unknowns),
+ ?line true = member({erlang, module_info, 0}, Unknowns),
+ ?line {ok, LC} = xref:q(s, "LC"),
+ ?line true = member({{read,bi,0},{read,bi,0}}, LC),
+
+ ?line ok = xref:set_library_path(s, add_erts_code_path(fname(code:lib_dir(kernel),ebin))),
+ ?line io:format("~p~n",[(catch xref:get_library_path(s))]),
+ ?line {ok, X2} = xref:q(s, "X"),
+ ?line ok = check_state(s),
+ ?line true = member({read, module_info, 0}, X2),
+ ?line false = member({foo, module_info, 0}, X2),
+ ?line true = member({erlang, module_info, 0}, X2),
+ ?line {ok, Unknowns2} = xref:q(s, "U"),
+ ?line false = member({read, module_info, 0}, Unknowns2),
+ ?line true = member({foo, module_info, 0}, Unknowns2),
+ ?line false = member({erlang, module_info, 0}, Unknowns2),
+
+ ?line ok = xref:remove_module(s, read),
+ ?line {ok, read} = xref:add_module(s, File, [{builtins,true}]),
+
+ UnresB = to_external(relation_to_family(converse(from_term(U)))),
+ ?line {ok, UnresB} = xref:q(s, "(Lin) UC"),
+ CallsB = to_external(relation_to_family(converse(from_term(OKB)))),
+ ?line {ok, CallsB} = xref:q(s, "(Lin) (E - UC) "),
+ ?line ok = check_state(s),
+ ?line {ok, XU} = xref:q(s, "XU"),
+ ?line Erl = set([{erlang,length,1},{erlang,integer,1},
+ {erlang,binary_to_term,1}]),
+ ?line [{erlang,binary_to_term,1},{erlang,length,1}] =
+ to_external(intersection(set(XU), Erl)),
+ ?line xref:stop(s).
+
+%% What is expected when xref_SUITE_data/read/read.erl is added:
+read_expected(Version) ->
+ %% Line positions in xref_SUITE_data/read/read.erl:
+ POS1 = 28, POS2 = POS1+10, POS3 = POS2+6, POS4 = POS3+6, POS5 = POS4+10,
+ POS6 = POS5+5, POS7 = POS6+6, POS8 = POS7+6, POS9 = POS8+8,
+ POS10 = POS9+10, POS11 = POS10+7, POS12 = POS11+8, POS13 = POS12+10,
+ POS14 = POS13+18, % POS15 = POS14+23,
+
+ FF = {read,funfuns,0},
+ U = [{POS1+5,{FF,{dist,'$F_EXPR',0}}},
+ {POS1+8,{FF,{dist,'$F_EXPR',0}}},
+ {POS2+8,{{read,funfuns,0},{expr,'$F_EXPR',1}}},
+ {POS3+4,{FF,{expr,'$F_EXPR',2}}},
+ {POS4+2,{FF,{modul,'$F_EXPR',1}}},
+ {POS4+4,{FF,{spm,'$F_EXPR',1}}},
+ {POS4+6,{FF,{spm,'$F_EXPR',1}}},
+ {POS4+8,{FF,{spm,'$F_EXPR',1}}},
+ {POS5+1,{FF,{'$M_EXPR','$F_EXPR',0}}},
+ {POS5+2,{FF,{'$M_EXPR','$F_EXPR',0}}},
+ {POS5+3,{FF,{'$M_EXPR','$F_EXPR',0}}},
+ {POS6+1,{FF,{'$M_EXPR','$F_EXPR',0}}},
+ {POS6+2,{FF,{'$M_EXPR','$F_EXPR',0}}},
+ {POS6+4,{FF,{n,'$F_EXPR',-1}}},
+ {POS7+1,{FF,{'$M_EXPR',f,1}}},
+ {POS7+2,{FF,{'$M_EXPR',f,1}}},
+ {POS8+2,{FF,{hej,'$F_EXPR',1}}},
+ {POS8+3,{FF,{t,'$F_EXPR',1}}},
+ {POS8+5,{FF,{a,'$F_EXPR',1}}},
+ {POS8+7,{FF,{m,'$F_EXPR',1}}},
+ {POS9+1,{FF,{'$M_EXPR',f,1}}},
+ {POS9+3,{FF,{a,'$F_EXPR',1}}},
+ {POS10+1,{FF,{'$M_EXPR',foo,1}}},
+ {POS10+2,{FF,{'$M_EXPR','$F_EXPR',1}}},
+ {POS10+3,{FF,{'$M_EXPR','$F_EXPR',2}}},
+ {POS10+4,{FF,{'$M_EXPR','$F_EXPR',1}}},
+ {POS10+5,{FF,{'$M_EXPR',san,1}}},
+ {POS10+6,{FF,{'$M_EXPR','$F_EXPR',1}}},
+ {POS11+1,{FF,{'$M_EXPR','$F_EXPR',1}}},
+ {POS11+2,{FF,{'$M_EXPR','$F_EXPR',-1}}},
+ {POS11+3,{FF,{m,f,-1}}},
+ {POS11+4,{FF,{m,f,-1}}},
+ {POS11+5,{FF,{'$M_EXPR','$F_EXPR',1}}},
+ {POS11+6,{FF,{'$M_EXPR','$F_EXPR',1}}},
+ {POS12+1,{FF,{'$M_EXPR','$F_EXPR',-1}}},
+ {POS12+4,{FF,{'$M_EXPR','$F_EXPR',2}}},
+ {POS12+7,{FF,{'$M_EXPR','$F_EXPR',-1}}},
+ {POS12+8,{FF,{m4,f4,-1}}},
+ {POS13+2,{FF,{debug,'$F_EXPR',0}}},
+ {POS13+3,{FF,{'$M_EXPR','$F_EXPR',-1}}},
+ {POS14+8,{{read,bi,0},{'$M_EXPR','$F_EXPR',1}}}],
+
+ O1 = [{0,{FF,{modul,'$F_EXPR',179}}},
+ {0,{FF,{read,'$F_EXPR',178}}},
+ {20,{{read,lc,0},{ets,new,0}}},
+ {21,{{read,lc,0},{ets,tab2list,1}}},
+ {POS1+1,{FF,{erlang,spawn,1}}},
+ {POS1+1,{FF,{mod17,fun17,0}}},
+ {POS1+2,{FF,{erlang,spawn,1}}},
+ {POS1+2,{FF,{read,local,0}}},
+ {POS1+3,{FF,{erlang,spawn,1}}},
+ {POS1+4,{FF,{dist,func,0}}},
+ {POS1+4,{FF,{erlang,spawn,1}}},
+ {POS1+5,{FF,{erlang,spawn,1}}},
+ {POS1+6,{FF,{erlang,spawn_link,1}}},
+ {POS1+6,{FF,{mod17,fun17,0}}},
+ {POS1+7,{FF,{dist,func,0}}},
+ {POS1+7,{FF,{erlang,spawn_link,1}}},
+ {POS1+8,{FF,{erlang,spawn_link,1}}},
+ {POS2+1,{FF,{d,f,0}}},
+ {POS2+1,{FF,{dist,func,2}}},
+ {POS2+1,{FF,{erlang,spawn,2}}},
+ {POS2+2,{FF,{dist,func,2}}},
+ {POS2+2,{FF,{erlang,spawn,2}}},
+ {POS2+2,{FF,{mod42,func,0}}},
+ {POS2+3,{FF,{d,f,0}}},
+ {POS2+3,{FF,{dist,func,2}}},
+ {POS2+3,{FF,{erlang,spawn_link,2}}},
+ {POS2+4,{FF,{dist,func,2}}},
+ {POS2+4,{FF,{erlang,spawn_link,2}}},
+ {POS2+4,{FF,{mod42,func,0}}},
+ {POS3+1,{FF,{dist,func,2}}},
+ {POS3+3,{FF,{dist,func,2}}},
+ {POS4+1,{FF,{erlang,spawn,4}}},
+ {POS4+1,{FF,{modul,function,0}}},
+ {POS4+2,{FF,{erlang,spawn,4}}},
+ {POS4+3,{FF,{dist,func,2}}},
+ {POS4+3,{FF,{erlang,spawn,4}}},
+ {POS4+3,{FF,{spm,spf,2}}},
+ {POS4+4,{FF,{dist,func,2}}},
+ {POS4+4,{FF,{erlang,spawn,4}}},
+ {POS4+5,{FF,{dist,func,2}}},
+ {POS4+5,{FF,{erlang,spawn_link,4}}},
+ {POS4+5,{FF,{spm,spf,2}}},
+ {POS4+6,{FF,{dist,func,2}}},
+ {POS4+6,{FF,{erlang,spawn_link,4}}},
+ {POS4+7,{FF,{erlang,spawn_opt,4}}},
+ {POS4+7,{FF,{read,bi,0}}},
+ {POS4+7,{FF,{spm,spf,2}}},
+ {POS4+8,{FF,{erlang,spawn_opt,4}}},
+ {POS4+8,{FF,{read,bi,0}}},
+ {POS5+1,{FF,{erlang,spawn,1}}},
+ {POS5+2,{FF,{erlang,spawn,1}}},
+ {POS5+3,{FF,{erlang,spawn_link,1}}},
+ {POS6+1,{FF,{erlang,spawn,2}}},
+ {POS6+2,{FF,{erlang,spawn_link,2}}},
+ {POS7+1,{FF,{erlang,spawn,4}}},
+ {POS7+2,{FF,{erlang,spawn_opt,4}}},
+ {POS8+1,{FF,{hej,san,1}}},
+ {POS8+4,{FF,{a,b,1}}},
+ {POS8+4,{FF,{erlang,apply,2}}},
+ {POS8+5,{FF,{erlang,apply,2}}},
+ {POS8+6,{FF,{erlang,apply,3}}},
+ {POS8+6,{FF,{m,f,1}}},
+ {POS8+7,{FF,{erlang,apply,3}}},
+ {POS9+1,{FF,{erlang,apply,3}}},
+ {POS9+1,{FF,{read,bi,0}}},
+ {POS9+2,{FF,{a,b,1}}},
+ {POS9+2,{FF,{erlang,apply,2}}},
+ {POS9+3,{FF,{erlang,apply,2}}},
+ {POS9+4,{FF,{erlang,apply,2}}},
+ {POS9+4,{FF,{erlang,not_a_function,1}}},
+ {POS9+5,{FF,{erlang,apply,3}}},
+ {POS9+5,{FF,{mod,func,2}}},
+ {POS9+6,{FF,{erlang,apply,1}}},
+ {POS9+7,{FF,{erlang,apply,2}}},
+ {POS9+7,{FF,{math,add3,1}}},
+ {POS9+8,{FF,{q,f,1}}},
+ {POS10+4,{FF,{erlang,apply,2}}},
+ {POS10+5,{FF,{mod1,fun1,1}}},
+ {POS11+1,{FF,{erlang,apply,3}}},
+ {POS11+2,{FF,{erlang,apply,3}}},
+ {POS11+3,{FF,{erlang,apply,3}}},
+ {POS11+4,{FF,{erlang,apply,3}}},
+ {POS11+6,{FF,{erlang,apply,2}}},
+ {POS12+1,{FF,{erlang,apply,2}}},
+ {POS12+4,{FF,{erlang,apply,2}}},
+ {POS12+5,{FF,{erlang,apply,3}}},
+ {POS12+5,{FF,{m3,f3,2}}},
+ {POS12+7,{FF,{erlang,apply,2}}},
+ {POS12+8,{FF,{erlang,apply,3}}},
+ {POS13+1,{FF,{dm,df,1}}},
+ {POS13+6,{{read,bi,0},{foo,module_info,0}}},
+ {POS13+7,{{read,bi,0},{read,module_info,0}}},
+ {POS13+9,{{read,bi,0},{t,foo,1}}},
+ {POS14+11,{{read,bi,0},{erlang,module_info,0}}},
+ {POS14+17,{{read,bi,0},{read,bi,0}}}],
+
+ OK = case Version of
+ abstract_v1 ->
+ [{POS8+3, {FF,{erlang,apply,3}}},
+ {POS10+1, {FF,{erlang,apply,3}}},
+ {POS10+6, {FF,{erlang,apply,3}}}]
+ ++ O1;
+ _ ->
+% [{POS15+2,{{read,bi,0},{foo,t,0}}},
+% {POS15+3,{{read,bi,0},{bar,t,0}}},
+% {POS15+6,{{read,bi,0},{read,local,0}}},
+% {POS15+8,{{read,bi,0},{foo,t,0}}},
+% {POS15+10,{{read,bi,0},{bar,t,0}}}] ++
+ O1
+ end,
+
+ %% When builtins =:= true:
+ OKB = [{POS13+1,{FF,{erts_debug,apply,4}}},
+ {POS13+2,{FF,{erts_debug,apply,4}}},
+ {POS13+3,{FF,{erts_debug,apply,4}}},
+ {POS1+3, {FF,{erlang,binary_to_term,1}}},
+ {POS3+1, {FF,{erlang,spawn,3}}},
+ {POS3+2, {FF,{erlang,spawn,3}}},
+ {POS3+3, {FF,{erlang,spawn_link,3}}},
+ {POS3+4, {FF,{erlang,spawn_link,3}}},
+ {POS6+4, {FF,{erlang,spawn,3}}},
+ {POS13+5, {{read,bi,0},{erlang,length,1}}},
+ {POS14+3, {{read,bi,0},{erlang,length,1}}}]
+ ++ OK,
+
+ {U, OK, OKB}.
+
+read2(suite) -> [];
+read2(doc) -> ["Data read from the Abstract Code (cont)"];
+read2(Conf) when is_list(Conf) ->
+ %% Handles the spawn_opt versions added in R9 (OTP-4180).
+ %% Expected augmentations: try/catch, cond.
+ CopyDir = ?copydir,
+ Dir = fname(CopyDir,"read"),
+ File = fname(Dir, "read2.erl"),
+ MFile = fname(Dir, "read2"),
+ Beam = fname(Dir, "read2.beam"),
+ Test = <<"-module(read2).
+ -compile(export_all).
+
+ f() ->
+ spawn_opt({read2,f}, % POS2
+ [f()]),
+ spawn_opt(fun() -> foo end, [link]),
+ spawn_opt(f(),
+ {read2,f}, [{min_heap_size,1000}]),
+ spawn_opt(f(),
+ fun() -> f() end, [flopp]),
+ spawn_opt(f(),
+ read2, f, [], []);
+ f() ->
+ %% Duplicated unresolved calls are ignored:
+ (f())(foo,bar),(f())(foo,bar). % POS1
+ ">>,
+ ?line ok = file:write_file(File, Test),
+ ?line {ok, read2} = compile:file(File, [debug_info,{outdir,Dir}]),
+
+ ?line {ok, _} = xref:start(s),
+ ?line {ok, read2} = xref:add_module(s, MFile),
+ ?line {U0, OK0} = read2_expected(),
+
+ U = to_external(relation_to_family(converse(from_term(U0)))),
+ OK = to_external(relation_to_family(converse(from_term(OK0)))),
+ ?line {ok, U2} = xref:q(s, "(Lin) UC"),
+ ?line {ok, OK2} = xref:q(s, "(Lin) (E - UC)"),
+ ?line true = U =:= U2,
+ ?line true = OK =:= OK2,
+ ?line ok = check_state(s),
+ ?line xref:stop(s),
+
+ ?line ok = file:delete(File),
+ ?line ok = file:delete(Beam),
+ ok.
+
+
+read2_expected() ->
+ POS1 = 16,
+ POS2 = 5,
+ FF = {read2,f,0},
+ U = [{POS1,{FF,{'$M_EXPR','$F_EXPR',2}}}],
+ OK = [{POS2,{FF,{erlang,spawn_opt,2}}},
+ {POS2,{FF,FF}},
+ {POS2+1,{FF,FF}},
+ {POS2+2,{FF,{erlang,spawn_opt,2}}},
+ {POS2+3,{FF,{erlang,spawn_opt,3}}},
+ {POS2+3,{FF,FF}},
+ {POS2+3,{FF,FF}},
+ {POS2+5,{FF,{erlang,spawn_opt,3}}},
+ {POS2+5,{FF,FF}},
+ {POS2+6,{FF,FF}},
+ {POS2+7,{FF,{erlang,spawn_opt,5}}},
+ {POS2+7,{FF,FF}},
+ {POS2+7,{FF,FF}},
+ {POS1,{FF,FF}}],
+ {U, OK}.
+
+remove(suite) -> [];
+remove(doc) -> ["Remove modules, applications, releases"];
+remove(Conf) when is_list(Conf) ->
+ S = new(),
+ ?line {error, _, {no_such_module, mod}} =
+ xref_base:remove_module(S, mod),
+ ?line {error, _, {no_such_application, app}} =
+ xref_base:remove_application(S, app),
+ ?line {error, _, {no_such_release, rel}} =
+ xref_base:remove_release(S, rel),
+ ?line ok = xref_base:delete(S),
+ ok.
+
+replace(suite) -> [];
+replace(doc) -> ["Replace modules, applications, releases"];
+replace(Conf) when is_list(Conf) ->
+ CopyDir = ?copydir,
+ Dir = fname(CopyDir,"rel2"),
+ X = fname(Dir, "x.erl"),
+ Y = fname(Dir, "y.erl"),
+ A1_0 = fname(Dir, fname("lib","app1-1.0")),
+ A1_1 = fname(Dir, fname("lib","app1-1.1")),
+ A2 = fname(Dir, fname("lib","app2-1.1")),
+ EB1_0 = fname(A1_0, "ebin"),
+ EB1_1 = fname(A1_1, "ebin"),
+ Xbeam = fname(EB1_1, "x.beam"),
+ Ybeam = fname(EB1_1, "y.beam"),
+
+ ?line {ok, x} = compile:file(X, [debug_info, {outdir,EB1_0}]),
+ ?line {ok, x} = compile:file(X, [debug_info, {outdir,EB1_1}]),
+ ?line {ok, y} = compile:file(Y, [debug_info, {outdir,EB1_1}]),
+
+ ?line {ok, _} = start(s),
+ ?line {ok, false} = xref:set_default(s, verbose, false),
+ ?line {ok, true} = xref:set_default(s, warnings, false),
+ ?line {ok, rel2} = xref:add_release(s, Dir, []),
+ ?line {error, _, _} = xref:replace_application(s, app1, "no_data"),
+ ?line {error, _, {no_such_application, app12}} =
+ xref:replace_application(s, app12, A1_0, []),
+ ?line {error, _, {invalid_filename,{foo,bar}}} =
+ xref:replace_application(s, app1, {foo,bar}, []),
+ ?line {error, _, {invalid_options,[not_an_option]}} =
+ xref:replace_application(s, foo, bar, [not_an_option]),
+ ?line {error, _, {invalid_options,[{builtins,not_a_value}]}} =
+ xref:replace_application(s, foo, bar, [{builtins,not_a_value}]),
+ ?line {ok, app1} =
+ xref:replace_application(s, app1, A1_0),
+ ?line [{_, AppInfo}] = xref:info(s, applications, app1),
+ ?line {value, {release, [rel2]}} = keysearch(release, 1, AppInfo),
+
+ ?line {error, _, {no_such_module, xx}} =
+ xref:replace_module(s, xx, Xbeam, []),
+ ?line {error, _, {invalid_options,[{builtins,true},not_an_option]}} =
+ xref:replace_module(s, foo, bar,[{builtins,true},not_an_option]),
+ ?line {error, _, {invalid_options,[{builtins,not_a_value}]}} =
+ xref:replace_module(s, foo, bar, [{builtins,not_a_value}]),
+ ?line {error, _, {invalid_filename,{foo,bar}}} =
+ xref:replace_module(s, x, {foo,bar}),
+ ?line {ok, x} = xref:replace_module(s, x, Xbeam),
+ ?line [{x, ModInfo}] = xref:info(s, modules, x),
+ ?line {value, {application, [app1]}} =
+ keysearch(application, 1, ModInfo),
+
+ ?line {ok, x} = compile:file(X, [no_debug_info, {outdir,EB1_1}]),
+ ?line {error, _, {no_debug_info, _}} = xref:replace_module(s, x, Xbeam),
+ ?line {error, _, {module_mismatch, x,y}} =
+ xref:replace_module(s, x, Ybeam),
+ ?line case os:type() of
+ {unix, _} ->
+ ?line hide_file(Ybeam),
+ ?line {error, _, {file_error, _, _}} =
+ xref:replace_module(s, x, Ybeam);
+ _ ->
+ true
+ end,
+ ?line ok = xref:remove_module(s, x),
+ ?line {error, _, {no_debug_info, _}} = xref:add_module(s, Xbeam),
+
+ %% "app2" is ignored, the old application name is kept
+ ?line {ok, app1} = xref:replace_application(s, app1, A2),
+
+ ?line xref:stop(s),
+ ?line ok = file:delete(fname(EB1_0, "x.beam")),
+ ?line ok = file:delete(Xbeam),
+ ?line ok = file:delete(Ybeam),
+ ok.
+
+update(suite) -> [];
+update(doc) -> ["The update() function"];
+update(Conf) when is_list(Conf) ->
+ CopyDir = ?copydir,
+ Dir = fname(CopyDir,"update"),
+ Source = fname(Dir, "x.erl"),
+ Beam = fname(Dir, "x.beam"),
+ ?line copy_file(fname(Dir, "x.erl.1"), Source),
+ ?line {ok, x} = compile:file(Source, [debug_info, {outdir,Dir}]),
+
+ ?line {ok, _} = start(s),
+ ?line ok = xref:set_default(s, [{verbose,false}, {warnings, false}]),
+ ?line {ok, [x]} = xref:add_directory(s, Dir, [{builtins,true}]),
+ ?line {error, _, {invalid_options,[not_an_option]}} =
+ xref:update(s, [not_an_option]),
+ ?line {ok, []} = xref:update(s),
+ ?line {ok, [{erlang,atom_to_list,1}]} = xref:q(s, "XU"),
+
+ ?line [{x, ModInfo}] = xref:info(s, modules, x),
+ ?line case keysearch(directory, 1, ModInfo) of
+ {value, {directory, Dir}} -> ok
+ end,
+
+ timer:sleep(2000), % make sure modification time has changed
+ ?line copy_file(fname(Dir, "x.erl.2"), Source),
+ ?line {ok, x} = compile:file(Source, [debug_info, {outdir,Dir}]),
+ ?line {ok, [x]} = xref:update(s, []),
+ ?line {ok, [{erlang,list_to_atom,1}]} = xref:q(s, "XU"),
+
+ timer:sleep(2000),
+ ?line {ok, x} = compile:file(Source, [no_debug_info,{outdir,Dir}]),
+ ?line {error, _, {no_debug_info, _}} = xref:update(s),
+
+ ?line xref:stop(s),
+ ?line ok = file:delete(Beam),
+ ?line ok = file:delete(Source),
+ ok.
+
+deprecated(suite) -> [];
+deprecated(doc) -> ["OTP-4695: Deprecated functions."];
+deprecated(Conf) when is_list(Conf) ->
+ Dir = ?copydir,
+ File = fname(Dir, "depr.erl"),
+ MFile_r9c = fname(Dir, "depr_r9c"),
+ MFile = fname(Dir, "depr"),
+ Beam = fname(Dir, "depr.beam"),
+ %% This file has been compiled to ?datadir/depr_r9c.beam
+ %% using the R9C compiler. From R10B and onwards the linter
+ %% checks the 'deprecated' attribute as well.
+% Test = <<"-module(depr).
+
+% -export([t/0,f/1,bar/2,f/2,g/3]).
+
+% -deprecated([{f,1}, % DF
+% {bar,2,eventually}]). % DF_3
+% -deprecated([{f,1,next_major_release}]). % DF_2 (again)
+% -deprecated([{frutt,0,next_version}]). % message...
+% -deprecated([{f,2,next_major_release}, % DF_2
+% {g,3,next_version}, % DF_1
+% {ignored,10,100}]). % message...
+% -deprecated([{does_not_exist,1}]). % message...
+
+% -deprecated(foo). % message...
+
+% t() ->
+% frutt(1),
+% g(1,2, 3),
+% ?MODULE:f(10).
+
+% f(A) ->
+% ?MODULE:f(A,A).
+
+% f(X, Y) ->
+% ?MODULE:g(X, Y, X).
+
+% g(F, G, H) ->
+% ?MODULE:bar(F, {G,H}).
+
+% bar(_, _) ->
+% true.
+
+% frutt(_) ->
+% frutt().
+
+% frutt() ->
+% true.
+% ">>,
+
+% ?line ok = file:write_file(File, Test),
+% ?line {ok, depr_r9c} = compile:file(File, [debug_info,{outdir,Dir}]),
+
+ ?line {ok, _} = xref:start(s),
+ ?line {ok, depr_r9c} = xref:add_module(s, MFile_r9c),
+ M9 = depr_r9c,
+ DF_1 = usort([{{M9,f,2},{M9,g,3}}]),
+ DF_2 = usort(DF_1++[{{M9,f,1},{M9,f,2}},{{M9,t,0},{M9,f,1}}]),
+ DF_3 = usort(DF_2++[{{M9,g,3},{M9,bar,2}}]),
+ DF = usort(DF_3++[{{M9,t,0},{M9,f,1}}]),
+
+ ?line {ok,DF} = xref:analyze(s, deprecated_function_calls),
+ ?line {ok,DF_1} =
+ xref:analyze(s, {deprecated_function_calls,next_version}),
+ ?line {ok,DF_2} =
+ xref:analyze(s, {deprecated_function_calls,next_major_release}),
+ ?line {ok,DF_3} =
+ xref:analyze(s, {deprecated_function_calls,eventually}),
+
+ D = to_external(range(from_term(DF))),
+ D_1 = to_external(range(from_term(DF_1))),
+ D_2 = to_external(range(from_term(DF_2))),
+ D_3 = to_external(range(from_term(DF_3))),
+
+ ?line {ok,D} = xref:analyze(s, deprecated_functions),
+ ?line {ok,D_1} =
+ xref:analyze(s, {deprecated_functions,next_version}),
+ ?line {ok,D_2} =
+ xref:analyze(s, {deprecated_functions,next_major_release}),
+ ?line {ok,D_3} =
+ xref:analyze(s, {deprecated_functions,eventually}),
+
+ ?line ok = check_state(s),
+ ?line xref:stop(s),
+
+ Test2= <<"-module(depr).
+
+ -export([t/0,f/1,bar/2,f/2,g/3]).
+
+ -deprecated([{'_','_',eventually}]). % DF_3
+ -deprecated([{f,'_',next_major_release}]). % DF_2
+ -deprecated([{g,'_',next_version}]). % DF_1
+ -deprecated([{bar,2}]). % DF
+
+ t() ->
+ g(1,2, 3),
+ ?MODULE:f(10).
+
+ f(A) ->
+ ?MODULE:f(A,A).
+
+ f(X, Y) ->
+ ?MODULE:g(X, Y, X).
+
+ g(F, G, H) ->
+ ?MODULE:bar(F, {G,H}).
+
+ bar(_, _) ->
+ ?MODULE:t().
+ ">>,
+
+ ?line ok = file:write_file(File, Test2),
+ ?line {ok, depr} = compile:file(File, [debug_info,{outdir,Dir}]),
+
+ ?line {ok, _} = xref:start(s),
+ ?line {ok, depr} = xref:add_module(s, MFile),
+
+ M = depr,
+ DFa_1 = usort([{{M,f,2},{M,g,3}}]),
+ DFa_2 = usort(DFa_1++[{{M,f,1},{M,f,2}},{{M,t,0},{M,f,1}}]),
+ DFa_3 = usort(DFa_2++[{{M,bar,2},{M,t,0}},{{M,g,3},{M,bar,2}}]),
+ DFa = DFa_3,
+
+ ?line {ok,DFa} = xref:analyze(s, deprecated_function_calls),
+ ?line {ok,DFa_1} =
+ xref:analyze(s, {deprecated_function_calls,next_version}),
+ ?line {ok,DFa_2} =
+ xref:analyze(s, {deprecated_function_calls,next_major_release}),
+ ?line {ok,DFa_3} =
+ xref:analyze(s, {deprecated_function_calls,eventually}),
+
+ ?line ok = check_state(s),
+ ?line xref:stop(s),
+
+ %% All of the module is deprecated.
+ Test3= <<"-module(depr).
+
+ -export([t/0,f/1,bar/2,f/2,g/3]).
+
+ -deprecated([{f,'_',next_major_release}]). % DF_2
+ -deprecated([{g,'_',next_version}]). % DF_1
+ -deprecated(module). % DF
+
+ t() ->
+ g(1,2, 3),
+ ?MODULE:f(10).
+
+ f(A) ->
+ ?MODULE:f(A,A).
+
+ f(X, Y) ->
+ ?MODULE:g(X, Y, X).
+
+ g(F, G, H) ->
+ ?MODULE:bar(F, {G,H}).
+
+ bar(_, _) ->
+ ?MODULE:t().
+ ">>,
+
+ ?line ok = file:write_file(File, Test3),
+ ?line {ok, depr} = compile:file(File, [debug_info,{outdir,Dir}]),
+
+ ?line {ok, _} = xref:start(s),
+ ?line {ok, depr} = xref:add_module(s, MFile),
+
+ DFb_1 = usort([{{M,f,2},{M,g,3}}]),
+ DFb_2 = usort(DFb_1++[{{M,f,1},{M,f,2}},{{M,t,0},{M,f,1}}]),
+ DFb_3 = DFb_2,
+ DFb = usort(DFb_2++[{{M,bar,2},{M,t,0}},{{M,g,3},{M,bar,2}}]),
+
+ ?line {ok,DFb} = xref:analyze(s, deprecated_function_calls),
+ ?line {ok,DFb_1} =
+ xref:analyze(s, {deprecated_function_calls,next_version}),
+ ?line {ok,DFb_2} =
+ xref:analyze(s, {deprecated_function_calls,next_major_release}),
+ ?line {ok,DFb_3} =
+ xref:analyze(s, {deprecated_function_calls,eventually}),
+
+ ?line ok = check_state(s),
+ ?line xref:stop(s),
+
+ ?line ok = file:delete(File),
+ ?line ok = file:delete(Beam),
+ ok.
+
+
+trycatch(suite) -> [];
+trycatch(doc) -> ["OTP-5152: try/catch, final (?) version."];
+trycatch(Conf) when is_list(Conf) ->
+ Dir = ?copydir,
+ File = fname(Dir, "trycatch.erl"),
+ MFile = fname(Dir, "trycatch"),
+ Beam = fname(Dir, "trycatch.beam"),
+ Test = <<"-module(trycatch).
+
+ -export([trycatch/0]).
+
+ trycatch() ->
+ try
+ foo:bar(),
+ bar:foo() of
+ 1 -> foo:foo();
+ 2 -> bar:bar()
+ catch
+ error:a -> err:e1();
+ error:b -> err:e2()
+ after
+ fini:shed()
+ end.
+ ">>,
+
+ ?line ok = file:write_file(File, Test),
+ ?line {ok, trycatch} = compile:file(File, [debug_info,{outdir,Dir}]),
+
+ ?line {ok, _} = xref:start(s),
+ ?line {ok, trycatch} = xref:add_module(s, MFile),
+ A = trycatch,
+ {ok,[{{{A,A,0},{bar,bar,0}},[10]},
+ {{{A,A,0},{bar,foo,0}},[8]},
+ {{{A,A,0},{err,e1,0}},[12]},
+ {{{A,A,0},{err,e2,0}},[13]},
+ {{{A,A,0},{fini,shed,0}},[15]},
+ {{{A,A,0},{foo,bar,0}},[7]},
+ {{{A,A,0},{foo,foo,0}},[9]}]} =
+ xref:q(s, "(Lin) (E | trycatch:trycatch/0)"),
+
+ ?line ok = check_state(s),
+ ?line xref:stop(s),
+
+ ?line ok = file:delete(File),
+ ?line ok = file:delete(Beam),
+ ok.
+
+
+abstract_modules(suite) -> [];
+abstract_modules(doc) -> ["OTP-5520: Abstract (parameterized) modules."];
+abstract_modules(Conf) when is_list(Conf) ->
+ Dir = ?copydir,
+ File = fname(Dir, "absmod.erl"),
+ MFile = fname(Dir, "absmod"),
+ Beam = fname(Dir, "absmod.beam"),
+ Test = <<"-module(param, [A, B]).
+
+ -export([args/1]).
+
+ args(C) ->
+ X = local(C),
+ Y = THIS:new(), % undef
+ Z = new(A, B),
+ {X, Y, Z}.
+
+ local(C) ->
+ module_info(C).
+ ">>,
+
+ ?line ok = file:write_file(File, Test),
+
+ %% The compiler will no longer allow us to have a mismatch between
+ %% the module name and the output file, so we must use a trick.
+ ?line {ok, param, BeamCode} = compile:file(File, [binary,debug_info]),
+ ?line ok = file:write_file(filename:join(Dir, Beam), BeamCode),
+
+ ?line {ok, _} = xref:start(s),
+ ?line {ok, param} = xref:add_module(s, MFile, {warnings,false}),
+ A = param,
+ ?line {ok, [{{{A,args,1},{'$M_EXPR',new,0}},[7]},
+ {{{A,args,1},{A,local,1}},[6]},
+ {{{A,args,1},{A,new,2}},[8]},
+ {{{A,local,1},{A,module_info,1}},[12]},
+ {{{param,new,2},{param,instance,2}},[0]}]} =
+ xref:q(s, "(Lin) E"),
+ ?line {ok,[{param,args,1},
+ {param,instance,2},
+ {param,local,1},
+ {param,module_info,1},
+ {param,new,2}]} = xref:q(s, "F"),
+
+ ?line ok = check_state(s),
+ ?line xref:stop(s),
+
+ ?line {ok, _} = xref:start(s, {xref_mode, modules}),
+ ?line {ok, param} = xref:add_module(s, MFile),
+ ?line {ok,[{param,args,1},
+ {param,instance,2},
+ {param,new,2}]} = xref:q(s, "X"),
+ ?line ok = check_state(s),
+ ?line xref:stop(s),
+
+ ?line ok = file:delete(File),
+ ?line ok = file:delete(Beam),
+ ok.
+
+fun_mfa(suite) -> [];
+fun_mfa(doc) -> ["OTP-5653: fun M:F/A."];
+fun_mfa(Conf) when is_list(Conf) ->
+ Dir = ?copydir,
+ File = fname(Dir, "fun_mfa.erl"),
+ MFile = fname(Dir, "fun_mfa"),
+ Beam = fname(Dir, "fun_mfa.beam"),
+ Test = <<"-module(fun_mfa).
+
+ -export([t/0, t1/0, t2/0, t3/0]).
+
+ t() ->
+ F = fun ?MODULE:t/0,
+ (F)().
+
+ t1() ->
+ F = fun t/0,
+ (F)().
+
+ t2() ->
+ fun ?MODULE:t/0().
+
+ t3() ->
+ fun t3/0().
+ ">>,
+
+ ?line ok = file:write_file(File, Test),
+ A = fun_mfa,
+ ?line {ok, A} = compile:file(File, [debug_info,{outdir,Dir}]),
+ ?line {ok, _} = xref:start(s),
+ ?line {ok, A} = xref:add_module(s, MFile, {warnings,false}),
+ ?line {ok, [{{{A,t,0},{'$M_EXPR','$F_EXPR',0}},[7]},
+ {{{A,t,0},{A,t,0}},[6]},
+ {{{A,t1,0},{'$M_EXPR','$F_EXPR',0}},[11]},
+ {{{A,t1,0},{A,t,0}},[10]},
+ {{{A,t2,0},{A,t,0}},[14]},
+ {{{A,t3,0},{fun_mfa,t3,0}},[17]}]} =
+ xref:q(s, "(Lin) E"),
+
+ ?line ok = check_state(s),
+ ?line xref:stop(s),
+
+ ?line ok = file:delete(File),
+ ?line ok = file:delete(Beam),
+ ok.
+
+qlc(suite) -> [];
+qlc(doc) -> ["OTP-5195: A bug fix when using qlc:q/1,2."];
+qlc(Conf) when is_list(Conf) ->
+ Dir = ?copydir,
+ File = fname(Dir, "qlc.erl"),
+ MFile = fname(Dir, "qlc"),
+ Beam = fname(Dir, "qlc.beam"),
+ Test = <<"-module(qlc).
+
+ -include_lib(\"stdlib/include/qlc.hrl\").
+
+ -export([t/0]).
+
+ t() ->
+ dets:open_file(t, []),
+ dets:insert(t, [{1,a},{2,b},{3,c},{4,d}]),
+ MS = ets:fun2ms(fun({X,Y}) when (X > 1) or (X < 5) -> {Y}
+ end),
+ QH1 = dets:table(t, [{traverse, {select, MS}}]),
+ QH2 = qlc:q([{Y} || {X,Y} <- dets:table(t),
+ (X > 1) or (X < 5)]),
+ true = qlc:info(QH1) =:= qlc:info(QH2),
+ dets:close(t),
+ ok.
+ ">>,
+
+ ?line ok = file:write_file(File, Test),
+ A = qlc,
+ ?line {ok, A} = compile:file(File, [debug_info,{outdir,Dir}]),
+ ?line {ok, _} = xref:start(s),
+ ?line {ok, A} = xref:add_module(s, MFile, {warnings,false}),
+ ?line {ok, _} = xref:q(s, "(Lin) E"), % is can be loaded
+
+ ?line ok = check_state(s),
+ ?line xref:stop(s),
+
+ ?line ok = file:delete(File),
+ ?line ok = file:delete(Beam),
+ ok.
+
+
+analyses(suite) ->
+ [analyze, basic, md, q, variables, unused_locals].
+
+analyze(suite) -> [];
+analyze(doc) -> ["Simple analyses"];
+analyze(Conf) when is_list(Conf) ->
+ S0 = new(),
+ ?line {{error, _, {invalid_options,[not_an_option]}}, _} =
+ xref_base:analyze(S0, undefined_function_calls, [not_an_option]),
+ ?line {{error, _, {invalid_query,{q}}}, _} = xref_base:q(S0,{q}),
+ ?line {{error, _, {unknown_analysis,foo}}, _} = xref_base:analyze(S0, foo),
+ ?line {{error, _, {unknown_constant,"foo:bar/-1"}}, _} =
+ xref_base:analyze(S0, {use,{foo,bar,-1}}),
+
+ CopyDir = ?copydir,
+ Dir = fname(CopyDir,"rel2"),
+ X = fname(Dir, "x.erl"),
+ Y = fname(Dir, "y.erl"),
+ A1_1 = fname([Dir,"lib","app1-1.1"]),
+ A2 = fname([Dir,"lib","app2-1.1"]),
+ EB1_1 = fname(A1_1, "ebin"),
+ EB2 = fname(A2, "ebin"),
+ Xbeam = fname(EB2, "x.beam"),
+ Ybeam = fname(EB1_1, "y.beam"),
+
+ ?line {ok, x} = compile:file(X, [debug_info, {outdir,EB2}]),
+ ?line {ok, y} = compile:file(Y, [debug_info, {outdir,EB1_1}]),
+
+ ?line {ok, rel2, S1} = xref_base:add_release(S0, Dir, [{verbose,false}]),
+ ?line S = set_up(S1),
+
+ ?line {ok, _} =
+ analyze(undefined_function_calls, [{{x,xx,0},{x,undef,0}}], S),
+ ?line {ok, _} = analyze(undefined_functions, [{x,undef,0}], S),
+ ?line {ok, _} = analyze(locals_not_used, [{x,l,0},{x,l1,0}], S),
+ ?line {ok, _} = analyze(exports_not_used, [{x,xx,0},{y,t,0}], S),
+
+ ?line {ok, _} =
+ analyze(deprecated_function_calls, [{{y,t,0},{x,t,0}}], S),
+ ?line {ok, _} = analyze({deprecated_function_calls,next_version}, [], S),
+ ?line {ok, _} =
+ analyze({deprecated_function_calls,next_major_release}, [], S),
+ ?line {ok, _} = analyze({deprecated_function_calls,eventually},
+ [{{y,t,0},{x,t,0}}], S),
+ ?line {ok, _} = analyze(deprecated_functions, [{x,t,0}], S),
+ ?line {ok, _} = analyze({deprecated_functions,next_version}, [], S),
+ ?line {ok, _} =
+ analyze({deprecated_functions,next_major_release}, [], S),
+ ?line {ok, _} = analyze({deprecated_functions,eventually}, [{x,t,0}], S),
+
+ ?line {ok, _} = analyze({call, {x,xx,0}}, [{x,undef,0}], S),
+ ?line {ok, _} =
+ analyze({call, [{x,xx,0},{x,l,0}]}, [{x,l1,0},{x,undef,0}], S),
+ ?line {ok, _} = analyze({use, {x,l,0}}, [{x,l1,0}], S),
+ ?line {ok, _} =
+ analyze({use, [{x,l,0},{x,l1,0}]}, [{x,l,0},{x,l1,0}], S),
+
+ ?line {ok, _} = analyze({module_call, x}, [x], S),
+ ?line {ok, _} = analyze({module_call, [x,y]}, [x], S),
+ ?line {ok, _} = analyze({module_use, x}, [x,y], S),
+ ?line {ok, _} = analyze({module_use, [x,y]}, [x,y], S),
+
+ ?line {ok, _} = analyze({application_call, app1}, [app2], S),
+ ?line {ok, _} = analyze({application_call, [app1,app2]}, [app2], S),
+ ?line {ok, _} = analyze({application_use, app2}, [app1,app2], S),
+ ?line {ok, _} = analyze({application_use, [app1,app2]}, [app1,app2], S),
+
+ ?line ok = xref_base:delete(S),
+ ?line ok = file:delete(Xbeam),
+ ?line ok = file:delete(Ybeam),
+ ok.
+
+basic(suite) -> [];
+basic(doc) -> ["Use of operators"];
+basic(Conf) when is_list(Conf) ->
+ ?line S0 = new(),
+
+ F1 = {m1,f1,1},
+ F6 = {m1,f2,6}, % X
+ F2 = {m2,f1,2},
+ F3 = {m2,f2,3}, % X
+ F7 = {m2,f3,7}, % X
+ F4 = {m3,f1,4}, % X
+ F5 = {m3,f2,5},
+
+ UF1 = {m1,f12,17},
+ UF2 = {m17,f17,177},
+
+ E1 = {F1,F3}, % X
+ E2 = {F6,F7}, % X
+ E3 = {F2,F6}, % X
+ E4 = {F1,F4}, % X
+ E5 = {F4,F5},
+ E6 = {F7,F4}, % X
+ E7 = {F1,F6},
+
+ UE1 = {F2,UF2}, % X
+ UE2 = {F5,UF1}, % X
+
+ D1 = {F1,12},
+ D6 = {F6,3},
+ DefAt_m1 = [D1,D6],
+ X_m1 = [F6],
+ % L_m1 = [F1],
+ XC_m1 = [E1,E2,E4],
+ LC_m1 = [E7],
+ LCallAt_m1 = [{E7,12}],
+ XCallAt_m1 = [{E1,13},{E2,17},{E4,7}],
+ Info1 = #xref_mod{name = m1, app_name = [a1]},
+ ?line S1 = add_module(S0, Info1, DefAt_m1, X_m1, LCallAt_m1, XCallAt_m1,
+ XC_m1, LC_m1),
+
+ D2 = {F2,7},
+ D3 = {F3,9},
+ D7 = {F7,19},
+ DefAt_m2 = [D2,D3,D7],
+ X_m2 = [F3,F7],
+ % L_m2 = [F2],
+ XC_m2 = [E3,E6,UE1],
+ LC_m2 = [],
+ LCallAt_m2 = [],
+ XCallAt_m2 = [{E3,96},{E6,12},{UE1,77}],
+ Info2 = #xref_mod{name = m2, app_name = [a2]},
+ ?line S2 = add_module(S1, Info2, DefAt_m2, X_m2, LCallAt_m2, XCallAt_m2,
+ XC_m2, LC_m2),
+
+ D4 = {F4,6},
+ D5 = {F5,97},
+ DefAt_m3 = [D4,D5],
+ X_m3 = [F4],
+ % L_m3 = [F5],
+ XC_m3 = [UE2],
+ LC_m3 = [E5],
+ LCallAt_m3 = [{E5,19}],
+ XCallAt_m3 = [{UE2,22}],
+ Info3 = #xref_mod{name = m3, app_name = [a3]},
+ ?line S3 = add_module(S2, Info3, DefAt_m3, X_m3, LCallAt_m3, XCallAt_m3,
+ XC_m3, LC_m3),
+
+ Info4 = #xref_mod{name = m4, app_name = [a2]},
+ ?line S4 = add_module(S3, Info4, [], [], [], [], [], []),
+
+ AppInfo1 = #xref_app{name = a1, rel_name = [r1]},
+ ?line S9 = add_application(S4, AppInfo1),
+ AppInfo2 = #xref_app{name = a2, rel_name = [r1]},
+ ?line S10 = add_application(S9, AppInfo2),
+ AppInfo3 = #xref_app{name = a3, rel_name = [r2]},
+ ?line S11 = add_application(S10, AppInfo3),
+
+ RelInfo1 = #xref_rel{name = r1},
+ ?line S12 = add_release(S11, RelInfo1),
+ RelInfo2 = #xref_rel{name = r2},
+ ?line S13 = add_release(S12, RelInfo2),
+
+ ?line S = set_up(S13),
+
+ ?line {ok, _} = eval("[m1,m2] + m:f/1", unknown_constant, S),
+ ?line {ok, _} = eval("[m1, m2, m:f/1]", type_mismatch, S),
+
+ ?line {ok, _} = eval("[m1, m1->m2]", type_mismatch, S),
+ ?line {ok, _} = eval("components:f/1", unknown_constant, S),
+ ?line {ok, _} = eval("'of':f/1", unknown_constant, S),
+ ?line {ok, _} = eval("of:f/1", parse_error, S),
+ ?line {ok, _} = eval("components", unknown_constant, S),
+ ?line {ok, _} = eval("[components, of, closure]", parse_error, S),
+ ?line {ok, _} = eval("[components, 'of', closure]", unknown_constant, S),
+
+ ?line {ok, _} = eval("[a1->a2,m1->m2]", type_mismatch, S),
+ ?line {ok, _} = eval("a1->a2,m1->m2", parse_error, S),
+
+ ?line {ok, _} = eval("m1->a1", type_mismatch, S),
+ ?line {ok, _} = eval("[{m1,f1,1}] : App", parse_error, S),
+ ?line {ok, _} = eval("[{m1,f1,1}] : Fun", [F1], S),
+ ?line {ok, _} = eval("range X", type_error, S),
+ ?line {ok, _} = eval("domain X", type_error, S),
+ ?line {ok, _} = eval("range M", type_error, S),
+ ?line {ok, _} = eval("domain M", type_error, S),
+
+ % Misc.
+ ?line {ok, _} = eval("not_a_prefix_operator m1", parse_error, S),
+ ?line {ok, _} = eval(f("(Mod) ~p", [[F1,F6,F5]]), [m1,m3], S),
+ ?line {ok, _} = eval("(Lin) M - (Lin) m1",
+ [{F2,7},{F3,9},{F7,19},{F4,6},{F5,97},{UF2,0}], S),
+ ?line {ok, _} = eval(f("(Lin) M * (Lin) ~p", [[F1,F6]]),
+ [{F1,12},{F6,3}], S),
+
+ ?line {ok, _} = eval(f("X * ~p", [[F1, F2, F3, F4, F5]]), [F3, F4], S),
+ ?line {ok, _} = eval("X", [F6,F3,F7,F4], S),
+ ?line {ok, _} = eval("X * AM", [F6,F3,F7,F4], S),
+ ?line {ok, _} = eval("X * a2", [F3,F7], S),
+
+ ?line {ok, _} = eval("L * r1", [F1,F2], S),
+ ?line {ok, _} = eval("U", [UF1, UF2], S),
+ ?line {ok, _} = eval("U * AM", [UF1], S),
+ ?line {ok, _} = eval("U * UM", [UF2], S),
+ ?line {ok, _} = eval("XU * [m1, m2]", [F6,F3,F7,UF1], S),
+ ?line {ok, _} = eval("LU * [m3, m4]", [F5], S),
+ ?line {ok, _} = eval("UU", [F1,F2], S),
+
+ ?line {ok, _} = eval("XC | m1", [E1,E2,E4], S),
+ ?line {ok, _} = eval(f("XC | ~p", [F1]), [E1,E4], S),
+ ?line {ok, _} = eval(f("(XXL) (Lin) (XC | ~p)", [F1]),
+ [{{D1,D3},[13]},{{D1,D4},[7]}],S),
+ ?line {ok, _} = eval(f("XC | (~p + ~p)", [F1, F2]), [E1,E4,E3,UE1], S),
+ ?line {ok, _} = eval(f("(XXL) (Lin) (XC | ~p)", [F1]),
+ [{{D1,D3},[13]},{{D1,D4},[7]}], S),
+ ?line {ok, _} = eval("LC | m3", [E5], S),
+ ?line {ok, _} = eval(f("LC | ~p", [F1]), [E7], S),
+ ?line {ok, _} = eval(f("LC | (~p + ~p)", [F1, F4]), [E7, E5], S),
+ ?line {ok, _} = eval("E | m1", [E1,E2,E4,E7], S),
+ ?line {ok, _} = eval(f("E | ~p", [F1]), [E1,E7,E4], S),
+ ?line {ok, _} = eval(f("E | (~p + ~p)", [F1, F2]), [E1,E7,E4,E3,UE1], S),
+
+ ?line {ok, _} = eval("XC || m1", [E3,UE2], S),
+ ?line {ok, _} = eval(f("XC || ~p", [F6]), [E3], S),
+ ?line {ok, _} = eval(f("XC || (~p + ~p)", [F4, UF2]), [UE1,E4,E6], S),
+ ?line {ok, _} = eval("LC || m3", [E5], S),
+ ?line {ok, _} = eval(f("LC || ~p", [F1]), [], S),
+ ?line {ok, _} = eval(f("LC || ~p", [F6]), [E7], S),
+ ?line {ok, _} = eval(f("LC || (~p + ~p)", [F5, F6]), [E7,E5], S),
+ ?line {ok, _} = eval("E || m1", [E3,UE2,E7], S),
+ ?line {ok, _} = eval(f("E || ~p", [F6]), [E3,E7], S),
+ ?line {ok, _} = eval(f("E || (~p + ~p)", [F3,F4]), [E1,E4,E6], S),
+
+ ?line {ok, _} = eval(f("~p + ~p", [F1,F2]), [F1,F2], S),
+ ?line {ok, _} = eval(f("~p * ~p", [m1,[F1,F6,F2]]), [F1,F6], S),
+ ?line {ok, _} = eval(f("~p * ~p", [F1,F2]), [], S),
+
+ %% range, domain
+ ?line {ok, _} = eval("range (E || m1)", [F6,UF1], S),
+ ?line {ok, _} = eval("domain (E || m1)", [F1,F2,F5], S),
+ ?line {ok, _} = eval(f("E | domain ~p", [[E1, {F2,F4}]]),
+ [E1,E7,E4,E3,UE1], S),
+
+ %% components, condensation, use, call
+ ?line {ok, _} = eval("(Lin) components E", type_error, S),
+ ?line {ok, _} = eval("components (Lin) E", type_error, S),
+ ?line {ok, _} = eval("components V", type_error, S),
+ ?line {ok, _} = eval("components E + components E", type_error, S),
+
+ ?line {ok, _} = eval(f("range (closure E | ~p)", [[F1,F2]]),
+ [F6,F3,F7,F4,F5,UF1,UF2], S),
+ ?line {ok, _} =
+ eval(f("domain (closure E || ~p)", [[UF2,F7]]), [F1,F2,F6], S),
+ ?line {ok, _} = eval("components E", [], S),
+ ?line {ok, _} = eval("components (Mod) E", [[m1,m2,m3]], S),
+ ?line {ok, _} = eval("components closure (Mod) E", [[m1,m2,m3]], S),
+ ?line {ok, _} = eval("condensation (Mod) E",
+ [{[m1,m2,m3],[m1,m2,m3]},{[m1,m2,m3],[m17]}], S),
+ ?line {ok, _} = eval("condensation closure (Mod) E",
+ [{[m1,m2,m3],[m1,m2,m3]},{[m1,m2,m3],[m17]}], S),
+ ?line {ok, _} = eval("condensation closure closure closure (Mod) E",
+ [{[m1,m2,m3],[m1,m2,m3]},{[m1,m2,m3],[m17]}], S),
+ ?line {ok, _} = eval("weak condensation (Mod) E",
+ [{[m1,m2,m3],[m1,m2,m3]},{[m1,m2,m3],[m17]},{[m17],[m17]}], S),
+ ?line {ok, _} = eval("strict condensation (Mod) E",
+ [{[m1,m2,m3],[m17]}], S),
+ ?line {ok, _} = eval("range condensation (Mod) E",
+ [[m1,m2,m3],[m17]], S),
+ ?line {ok, _} = eval("domain condensation (Mod) E",
+ [[m1,m2,m3]], S),
+
+ %% |, ||, |||
+ ?line {ok, _} = eval("(Lin) E || V", type_error, S),
+ ?line {ok, _} = eval("E ||| (Lin) V", type_error, S),
+ ?line {ok, _} = eval("E ||| m1", [E7], S),
+ ?line {ok, _} = eval("closure E ||| m1", [E7,{F1,UF1},{F6,UF1}], S),
+ ?line {ok, _} = eval("closure E ||| [m1,m2]",
+ [{F1,UF1},{F2,F7},{F1,F7},{F6,UF1},{F2,UF1},{F7,UF1},E7,E1,E2,E3], S),
+ ?line {ok, _} = eval("AE | a1", [{a1,a1},{a1,a2},{a1,a3}], S),
+
+ %% path ('of')
+ ?line {ok, _} = eval("(Lin) {m1,m2} of E", type_error, S),
+ ?line {ok, _} = eval("{m1,m2} of (Lin) E", type_error, S),
+ ?line [m1,m2] = eval("{m1,m2} of {m1,m2}", S),
+ ?line {ok, _} = eval("{m1,m2} of m1", type_error, S),
+ ?line {ok, _} = eval("{a3,m1} of ME", type_mismatch, S),
+ ?line [m1,m1] = eval("{m1} of ME", S),
+ ?line [m1,m1] = eval("{m1} of closure closure ME", S),
+ ?line false = eval("{m17} of ME", S),
+ ?line [m2,m1,m2] = eval("{m2} : Mod of ME", S),
+ ?line [m1,m2,m17] = eval("{m1, m17} of ME", S),
+ ?line [m1,m2,m17] = eval("m1 -> m17 of ME", S),
+ ?line {ok, _} = eval("[m1->m17,m17->m1] of ME", type_error, S),
+ ?line case eval(f("~p of E", [{F1,F7,UF1}]), S) of
+ [F1,F6,F7,F4,F5,UF1] -> ok
+ end,
+ ?line [a2,a1,a2] = eval("{a2} of AE", S),
+
+ %% weak/strict
+ ?line {ok, _} = eval("weak {m1,m2}", [{m1,m1},{m1,m2},{m2,m2}], S),
+ ?line {ok, _} = eval("strict [{m1,m1},{m1,m2},{m2,m2}]", [{m1,m2}], S),
+ ?line {ok, _} = eval("range weak [{m1,m2}] : Mod", [m1,m2], S),
+ ?line {ok, _} = eval("domain strict [{m1,m1},{m1,m2},{m2,m2}]", [m1], S),
+
+ %% #, number of
+ ?line {ok, _} = eval("# [{r1,r2}] : Rel", 1, S),
+ ?line {ok, _} = eval("# [{a3,a1}] : App", 1, S),
+ ?line {ok, _} = eval("# AE", 7, S),
+ ?line {ok, _} = eval("# ME", 8, S),
+ ?line {ok, _} = eval("# AE + # ME", 15, S),
+ ?line {ok, _} = eval("# AE * # ME", 56, S),
+ ?line {ok, _} = eval("# AE - # ME", -1, S),
+ ?line {ok, _} = eval("# E", 9, S),
+ ?line {ok, _} = eval("# V", 9, S),
+ ?line {ok, _} = eval("# (Lin) E", 9, S),
+ ?line {ok, _} = eval("# (ELin) E", 7, S),
+ ?line {ok, _} = eval("# closure E", type_error, S),
+ ?line {ok, _} = eval("# weak {m1,m2}", 3, S),
+ ?line {ok, _} = eval("#strict condensation (Mod) E", 1, S),
+ ?line {ok, _} = eval("#components closure (Mod) E", 1, S),
+ ?line {ok, _} = eval("# range strict condensation (Mod) E", 1, S),
+ ok.
+
+md(suite) -> [];
+md(doc) -> ["The xref:m() and xref:d() functions"];
+md(Conf) when is_list(Conf) ->
+ CopyDir = ?copydir,
+ Dir = fname(CopyDir,"md"),
+ X = fname(Dir, "x__x.erl"),
+ Y = fname(Dir, "y__y.erl"),
+ Xbeam = fname(Dir, "x__x.beam"),
+ Ybeam = fname(Dir, "y__y.beam"),
+
+ ?line {error, _, {invalid_filename,{foo,bar}}} = xref:m({foo,bar}),
+ ?line {error, _, {invalid_filename,{foo,bar}}} = xref:d({foo,bar}),
+
+ ?line {ok, x__x} = compile:file(X, [debug_info, {outdir,Dir}]),
+ ?line {ok, y__y} = compile:file(Y, [debug_info, {outdir,Dir}]),
+
+ ?line {error, _, {no_such_module, foo_bar}} = xref:m(foo_bar),
+ ?line OldPath = code:get_path(),
+ ?line true = code:set_path([Dir | OldPath]),
+ ?line MInfo = xref:m(x__x),
+ ?line [{{x__x,t,1},{y__y,t,2}}] = info_tag(MInfo, undefined),
+ ?line [] = info_tag(MInfo, unused),
+ ?line [] = info_tag(MInfo, deprecated),
+ ?line DInfo = xref:d(Dir),
+ ?line [{{x__x,t,1},{y__y,t,2}}] = info_tag(DInfo, undefined),
+ ?line [{y__y,l,0},{y__y,l1,0}] = info_tag(DInfo, unused),
+ ?line [] = info_tag(MInfo, deprecated),
+
+ %% Switch from 'functions' mode to 'modules' mode.
+ ?line {ok, x__x} = compile:file(X, [no_debug_info, {outdir,Dir}]),
+ ?line {ok, y__y} = compile:file(Y, [no_debug_info, {outdir,Dir}]),
+ ?line MInfoMod = xref:m(x__x),
+ ?line [{y__y,t,2}] = info_tag(MInfoMod, undefined),
+ ?line [] = info_tag(MInfo, deprecated),
+ ?line DInfoMod = xref:d(Dir),
+ ?line [{y__y,t,2}] = info_tag(DInfoMod, undefined),
+ ?line [] = info_tag(MInfo, deprecated),
+
+ ?line true = code:set_path(OldPath),
+ ?line ok = file:delete(Xbeam),
+ ?line ok = file:delete(Ybeam),
+ ok.
+
+q(suite) -> [];
+q(doc) -> ["User queries"];
+q(Conf) when is_list(Conf) ->
+ ?line S0 = new(),
+ ?line {ok, _} = eval("'foo", parse_error, S0),
+ ?line {ok, _} = eval("TT = E, TT = V", variable_reassigned, S0),
+ ?line {ok, _} = eval("TT = E, TTT", unknown_variable, S0),
+ ?line {ok, S} = eval("TT := E", [], S0),
+ ?line {ok, S1} = eval("TT * TT * TT", [], S),
+ ?line {ok, _S2} = xref_base:forget(S1, 'TT'),
+ ok.
+
+variables(suite) -> [];
+variables(doc) -> ["Setting and getting values of query variables"];
+variables(Conf) when is_list(Conf) ->
+ ?line Sa = new(),
+ ?line {{error, _, {invalid_options,[not_an_option]}}, _} =
+ xref_base:variables(Sa, [not_an_option]),
+ ?line {error, _, {not_user_variable,foo}} = xref_base:forget(Sa, foo),
+ ?line Sa1 = set_up(Sa),
+ ?line {error, _, {not_user_variable,foo}} = xref_base:forget(Sa1, foo),
+ ?line ok = xref_base:delete(Sa1),
+
+ ?line S0 = new(),
+
+ F1 = {m1,f1,1},
+ F2 = {m2,f1,2},
+ Lib = {lib1,f1,1}, % undefined
+
+ E1 = {F1,F2},
+ E2 = {F2,F1},
+ E3 = {F1,Lib},
+
+ D1 = {F1,12},
+ DefAt_m1 = [D1],
+ X_m1 = [F1],
+ % L_m1 = [],
+ XC_m1 = [E1,E3],
+ LC_m1 = [],
+ LCallAt_m1 = [],
+ XCallAt_m1 = [{E1,13},{E3,17}],
+ Info1 = #xref_mod{name = m1, app_name = [a1]},
+ ?line S1 = add_module(S0, Info1, DefAt_m1, X_m1, LCallAt_m1, XCallAt_m1,
+ XC_m1, LC_m1),
+
+ D2 = {F2,7},
+ DefAt_m2 = [D2],
+ X_m2 = [F2],
+ % L_m2 = [],
+ XC_m2 = [E2],
+ LC_m2 = [],
+ LCallAt_m2 = [],
+ XCallAt_m2 = [{E2,96}],
+ Info2 = #xref_mod{name = m2, app_name = [a2]},
+ ?line S2 = add_module(S1, Info2, DefAt_m2, X_m2, LCallAt_m2, XCallAt_m2,
+ XC_m2, LC_m2),
+
+ ?line S = set_up(S2),
+
+ ?line eval("T1=E, T2=E*T1, T3 = T2*T2, T4=range T3, T5=T3|T4, T5",
+ [E1,E2,E3], S),
+ ?line eval("((E*E)*(E*E)) | (range ((E*E)*(E*E)))",
+ [E1,E2,E3], S),
+ ?line eval("T1=V*V,T2=T1*V,T3=V*V*V,T3",
+ [F1,F2,Lib], S),
+ ?line eval("T1=V*V, T2=V*V, T1*T2",
+ [F1,F2,Lib], S),
+
+ ?line {ok, S100} = eval("T0 := E", [E1, E2, E3], S),
+ ?line {ok, S101} = eval("T1 := E | m1", [E1, E3], S100),
+ ?line {ok, S102} = eval("T2 := E | m2", [E2], S101),
+ ?line {{ok, [{user, ['T0', 'T1', 'T2']}]}, _} = xref_base:variables(S102),
+ ?line {ok, S103} = xref_base:forget(S102, 'T0'),
+ ?line {{ok, [{user, ['T1', 'T2']}]}, S104} =
+ xref_base:variables(S103, [user]),
+ ?line {ok, S105} = xref_base:forget(S104),
+ ?line {{ok, [{user, []}]}, S106} = xref_base:variables(S105),
+ ?line {{ok, [{predefined,_}]}, S107_0} =
+ xref_base:variables(S106, [predefined]),
+
+ ?line {ok, S107_1} =
+ eval("TT := E, TT2 := V, TT1 := TT * TT", [E1,E2,E3], S107_0),
+ ?line {{ok, [{user, ['TT', 'TT1', 'TT2']}]}, _} =
+ xref_base:variables(S107_1),
+ ?line {ok, S107} = xref_base:forget(S107_1),
+
+ CopyDir = ?copydir,
+ ?line Dir = fname(CopyDir,"lib_test"),
+ Beam = fname(Dir, "lib1.beam"),
+
+ ?line copy_file(fname(Dir, "lib1.erl"), Beam),
+ ?line {ok, S108} =
+ xref_base:set_library_path(S107, [Dir], [{verbose,false}]),
+ ?line {{error, _, _}, _} = xref_base:variables(S108, [{verbose,false}]),
+ ?line {ok, S109} = xref_base:set_library_path(S108, [], [{verbose,false}]),
+
+ ?line Tabs = length(ets:all()),
+
+ ?line {ok, S110} = eval("Eplus := closure E, TT := Eplus",
+ 'closure()', S109),
+ ?line {{ok, [{user, ['Eplus','TT']}]}, S111} = xref_base:variables(S110),
+ ?line {ok, S112} = xref_base:forget(S111, ['TT','Eplus']),
+ ?line true = Tabs =:= length(ets:all()),
+
+ ?line {ok, NS0} = eval("Eplus := closure E", 'closure()', S112),
+ ?line {{ok, [{user, ['Eplus']}]}, NS} = xref_base:variables(NS0),
+ ?line ok = xref_base:delete(NS),
+ ?line true = Tabs =:= length(ets:all()),
+
+ ?line ok = file:delete(Beam),
+ ok.
+
+unused_locals(suite) -> [];
+unused_locals(doc) -> ["OTP-5071. Too many unused functions."];
+unused_locals(Conf) when is_list(Conf) ->
+ Dir = ?copydir,
+
+ File1 = fname(Dir, "a.erl"),
+ MFile1 = fname(Dir, "a"),
+ Beam1 = fname(Dir, "a.beam"),
+ Test1 = <<"-module(a).
+ -export([f/1, g/2]).
+
+ f(X) ->
+ Y = b:f(X),
+ Z = b:g(Y),
+ start(b, h, [Z]).
+
+ g(X, Y) ->
+ ok.
+
+ start(M, F, A) ->
+ spawn(M, F, A).
+ ">>,
+ ?line ok = file:write_file(File1, Test1),
+ ?line {ok, a} = compile:file(File1, [debug_info,{outdir,Dir}]),
+
+ File2 = fname(Dir, "b.erl"),
+ MFile2 = fname(Dir, "b"),
+ Beam2 = fname(Dir, "b.beam"),
+ Test2 = <<"-module(b).
+ -export([f/1, g/2]).
+
+ f(X) ->
+ io:write(\"~w\", [X]),
+ a:start(timer, sleep, [1000]).
+
+ g(X, Y) ->
+ apply(a, g, [X, Y]).
+ ">>,
+
+ ?line ok = file:write_file(File2, Test2),
+ ?line {ok, b} = compile:file(File2, [debug_info,{outdir,Dir}]),
+
+ ?line {ok, _} = xref:start(s),
+ ?line {ok, a} = xref:add_module(s, MFile1),
+ ?line {ok, b} = xref:add_module(s, MFile2),
+ ?line {ok, []} = xref:analyse(s, locals_not_used),
+ ?line ok = check_state(s),
+ ?line xref:stop(s),
+
+ ?line ok = file:delete(File1),
+ ?line ok = file:delete(Beam1),
+ ?line ok = file:delete(File2),
+ ?line ok = file:delete(Beam2),
+ ok.
+
+misc(suite) ->
+ [format_error, otp_7423, otp_7831].
+
+format_error(suite) -> [];
+format_error(doc) -> ["Format error messages"];
+format_error(Conf) when is_list(Conf) ->
+ ?line {ok, _Pid} = start(s),
+ ?line ok = xref:set_default(s, [{verbose,false}, {warnings, false}]),
+
+ %% Parse error messages.
+ ?line 'Invalid regular expression "add(": unterminated \`(\'\n'
+ = fatom(xref:q(s,'"add("')),
+ ?line 'Invalid operator foo\n' =
+ fatom(xref:q(s,'foo E')),
+ ?line 'Invalid wildcard variable \'_Var\' (only \'_\' is allowed)\n'
+ = fatom(xref:q(s,"module:function/_Var")),
+ ?line 'Missing type of regular expression ".*"\n'
+ = fatom(xref:q(s,'".*"')),
+ ?line 'Type does not match structure of constant: \'M\' : Fun\n'
+ = fatom(xref:q(s,"'M' : Fun")),
+ ?line 'Type does not match structure of constant: ".*" : Fun\n'
+ = fatom(xref:q(s,'".*" : Fun')),
+ ?line 'Type does not match structure of constant: [m:f/1, m1:f2/3] : App\n'
+ = fatom(xref:q(s,"[m:f/1,m1:f2/3] : App")),
+ ?line 'Parse error on line 1: syntax error before: \'-\'\n' =
+ fatom(xref:q(s,"E + -")),
+ ?line "Parse error on line 1: unterminated atom starting with 'foo'\n"
+ = flatten(xref:format_error(xref:q(s,"'foo"))),
+ ?line 'Parse error at end of string: syntax error before: \n' =
+ fatom(xref:q(s,"E +")),
+ ?line 'Parse error on line 1: syntax error before: \'Lin\'\n' =
+ fatom(xref:q(s,"Lin")),
+
+ %% Other messages
+ ?line 'Variable \'QQ\' used before set\n' =
+ fatom(xref:q(s,"QQ")),
+ ?line 'Unknown constant a\n' =
+ fatom(xref:q(s,"{a} of E")),
+
+ %% Testing xref_parser:t2s/1.
+ ?line 'Variable assigned more than once: E := E + E\n' =
+ fatom(xref:q(s,"E:=E + E")),
+ ?line 'Variable assigned more than once: E = E + E\n' =
+ fatom(xref:q(s,"E=E + E")),
+ ?line "Operator applied to argument(s) of different or invalid type(s): "
+ "E + V * V\n" =
+ flatten(xref:format_error(xref:q(s,"E + (V * V)"))),
+ ?line {error,xref_compiler,{type_error,"(V + V) * E"}} =
+ xref:q(s,"(V + V) * E"),
+ ?line "Type does not match structure of constant: [m:f/3 -> g:h/17] : "
+ "App\n" =
+ flatten(xref:format_error(xref:q(s,"[{{m,f,3},{g,h,17}}] : App"))),
+ ?line 'Type does not match structure of constant: [m -> f, g -> h] : Fun\n'
+ = fatom(xref:q(s,"[{m,f},g->h] : Fun")),
+ ?line 'Type does not match structure of constant: {m, n, o} : Fun\n' =
+ fatom(xref:q(s,"{m,n,o} : Fun")),
+ ?line {error,xref_compiler,{type_error,"range (Lin) V"}} =
+ xref:q(s,"range ((Lin) V)"),
+ ?line {error,xref_compiler,{type_error,"condensation range E"}} =
+ xref:q(s,"condensation (range E)"),
+ ?line {error,xref_compiler,{type_error,"condensation (# E + # V)"}} =
+ xref:q(s,"condensation (# E + # V)"),
+ ?line {error,xref_compiler,{type_error,"range (# E + # E)"}} =
+ xref:q(s,"range (#E + #E)"),
+ ?line {error,xref_compiler,{type_error,"range (# E)"}} =
+ xref:q(s,"range #E"), % Hm...
+ ?line {error,xref_compiler,{type_error,"E + # E"}} =
+ xref:q(s,"E + #E + #E"), % Hm...
+ ?line {error,xref_compiler,{type_error,"V * E || V | V"}} =
+ xref:q(s,"V * (E || V) | V"),
+ ?line {error,xref_compiler,{type_error,"E || (E | V)"}} =
+ xref:q(s,"V * E || (E | V)"),
+ ?line {error,xref_compiler,{type_error,"E * \"m\" : Mod"}} =
+ xref:q(s,'E * "m" : Mod'),
+ ?line {error,xref_compiler,{type_error,"E * (\"m\":f/_ + m:\"f\"/3)"}} =
+ xref:q(s,'E * ("m":f/_ + m:"f"/3)'),
+
+ ?line xref:stop(s),
+ ok.
+
+otp_7423(suite) -> [];
+otp_7423(doc) -> ["OTP-7423. Xref scanner bug."];
+otp_7423(Conf) when is_list(Conf) ->
+ ?line {ok, _Pid} = start(s),
+ S = "E | [compiler] : App || [{erlang,
+ size,
+ 1}] : Fun",
+ ?line {error,xref_compiler,{unknown_constant,"compiler"}} = xref:q(s,S),
+ ?line xref:stop(s),
+ ok.
+
+otp_7831(suite) -> [];
+otp_7831(doc) -> ["OTP-7831. Allow anonymous Xref processes."];
+otp_7831(Conf) when is_list(Conf) ->
+ ?line {ok, Pid1} = xref:start([]),
+ ?line xref:stop(Pid1),
+ ?line {ok, Pid2} = xref:start([{xref_mode, modules}]),
+ ?line xref:stop(Pid2),
+ ok.
+
+%%%
+%%% Utilities
+%%%
+
+copy_file(Src, Dest) ->
+ file:copy(Src, Dest).
+
+fname(N) ->
+ filename:join(N).
+
+fname(Dir, Basename) ->
+ filename:join(Dir, Basename).
+
+new() ->
+ ?line {ok, S} = xref_base:new(),
+ S.
+
+set_up(S) ->
+ ?line {ok, S1} = xref_base:set_up(S, [{verbose, false}]),
+ S1.
+
+eval(Query, E, S) ->
+ ?format("------------------------------~n", []),
+ ?format("Evaluating ~p~n", [Query]),
+ ?line {Answer, NewState} = xref_base:q(S, Query, [{verbose, false}]),
+ {Reply, Expected} =
+ case Answer of
+ {ok, R} when is_list(E) ->
+ {unsetify(R), sort(E)};
+ {ok, R} ->
+ {unsetify(R), E};
+ {error, _Module, Reason} ->
+ {element(1, Reason), E}
+ end,
+ if
+ Reply =:= Expected ->
+ ?format("As expected, got ~n~p~n", [Expected]),
+ {ok, NewState};
+ true ->
+ ?format("Expected ~n~p~nbut got ~n~p~n", [Expected, Reply]),
+ not_ok
+ end.
+
+analyze(Query, E, S) ->
+ ?format("------------------------------~n", []),
+ ?format("Evaluating ~p~n", [Query]),
+ ?line {{ok, L}, NewState} =
+ xref_base:analyze(S, Query, [{verbose, false}]),
+ case {unsetify(L), sort(E)} of
+ {X,X} ->
+ ?format("As was expected, got ~n~p~n", [X]),
+ {ok, NewState};
+ {_R,_X} ->
+ ?format("Expected ~n~p~nbut got ~n~p~n", [_X, _R]),
+ not_ok
+ end.
+
+unsetify(S) ->
+ case is_sofs_set(S) of
+ true -> to_external(S);
+ false -> S
+ end.
+
+%% Note: assumes S has been set up; the new state is not returned
+eval(Query, S) ->
+ ?line {{ok, Answer}, _NewState} =
+ xref_base:q(S, Query, [{verbose, false}]),
+ unsetify(Answer).
+
+add_module(S, XMod, DefAt, X, LCallAt, XCallAt, XC, LC) ->
+ Attr = {[], [], []},
+ Depr0 = {[], [], [], []},
+ DBad = [],
+ Depr = {Depr0,DBad},
+ Data = {DefAt, LCallAt, XCallAt, LC, XC, X, Attr, Depr},
+ Unres = [],
+ ?line {ok, _Module, _Bad, State} =
+ xref_base:do_add_module(S, XMod, Unres, Data),
+ State.
+
+add_application(S, XApp) ->
+ ?line xref_base:do_add_application(S, XApp).
+
+add_release(S, XRel) ->
+ ?line xref_base:do_add_release(S, XRel).
+
+remove_module(S, M) ->
+ ?line xref_base:do_remove_module(S, M).
+
+info_tag(Info, Tag) ->
+ {value, {_Tag, Value}} = lists:keysearch(Tag, 1, Info),
+ Value.
+
+make_ufile(FileName) ->
+ ?line ok = file:write_file(FileName, term_to_binary(foo)),
+ ?line hide_file(FileName).
+
+make_udir(Dir) ->
+ ?line ok = file:make_dir(Dir),
+ ?line hide_file(Dir).
+
+hide_file(FileName) ->
+ ?line {ok, FileInfo} = file:read_file_info(FileName),
+ ?line NewFileInfo = FileInfo#file_info{mode = 0},
+ ?line ok = file:write_file_info(FileName, NewFileInfo).
+
+%% Note that S has to be set up before calling this checking function.
+check_state(S) ->
+ ?line Info = xref:info(S),
+
+ ?line modules_mode_check(S, Info),
+ case info(Info, mode) of
+ modules ->
+ ok;
+ functions ->
+ functions_mode_check(S, Info)
+ end.
+
+%% The manual mentions some facts that should always hold.
+%% Here they are again.
+functions_mode_check(S, Info) ->
+ %% F = L + X,
+ ?line {ok, F} = xref:q(S, "F"),
+ ?line {ok, F} = xref:q(S, "L + X"),
+
+ %% V = X + L + B + U,
+ ?line {ok, V} = xref:q(S, "V"),
+ ?line {ok, V} = xref:q(S, "X + L + B + U"),
+
+ %% X, L, B and U are disjoint.
+ ?line {ok, []} =
+ xref:q(S, "X * L + X * B + X * U + L * B + L * U + B * U"),
+
+ %% V = UU + XU + LU,
+ ?line {ok, V} = xref:q(S, "UU + XU + LU"),
+
+ %% E = LC + XC
+ ?line {ok, E} = xref:q(S, "E"),
+ ?line {ok, E} = xref:q(S, "LC + XC"),
+
+ %% U subset of XU,
+ ?line {ok, []} = xref:q(S, "U - XU"),
+
+ %% LU = range LC
+ ?line {ok, []} = xref:q(S, "(LU - range LC) + (range LC - LU)"),
+
+ %% XU = range XC
+ ?line {ok, []} = xref:q(S, "(XU - range XC) + (range XC - XU)"),
+
+ %% LU subset F
+ ?line {ok, []} = xref:q(S, "LU - F"),
+
+ %% UU subset F
+ ?line {ok, []} = xref:q(S, "UU - F"),
+
+ %% ME = (Mod) E
+ ?line {ok, ME} = xref:q(S, "ME"),
+ ?line {ok, ME} = xref:q(S, "(Mod) E"),
+
+ %% AE = (App) E
+ ?line {ok, AE} = xref:q(S, "AE"),
+ ?line {ok, AE} = xref:q(S, "(App) E"),
+
+ %% RE = (Rel) E
+ ?line {ok, RE} = xref:q(S, "RE"),
+ ?line {ok, RE} = xref:q(S, "(Rel) E"),
+
+ %% (Mod) V subset of M
+ ?line {ok, []} = xref:q(S, "(Mod) V - M"),
+
+ %% range UC subset of U
+ ?line {ok, []} = xref:q(S, "range UC - U"),
+
+ %% Some checks on the numbers returned by the info functions.
+
+ ?line {Resolved, Unresolved} = info(Info, no_calls),
+ ?line AllCalls = Resolved + Unresolved,
+ ?line {ok, AllCalls} = xref:q(S, "# (XLin) E + # (LLin) E"),
+
+ ?line {Local, Exported} = info(Info, no_functions),
+ ?line LX = Local+Exported,
+ ?line {ok, LXs} = xref:q(S, 'Extra = _:module_info/"(0|1)" + LM,
+ # (F - Extra)'),
+ ?line true = LX =:= LXs,
+
+ ?line {LocalCalls, ExternalCalls, UnresCalls} =
+ info(Info, no_function_calls),
+ ?line LEU = LocalCalls + ExternalCalls + UnresCalls,
+ ?line {ok, LEU} = xref:q(S, "# LC + # XC"),
+
+ ?line InterFunctionCalls = info(Info, no_inter_function_calls),
+ ?line {ok, InterFunctionCalls} = xref:q(S, "# EE"),
+
+ %% And some more checks on counters...
+ ?line check_count(S),
+
+ %% ... and more
+ ?line {ok, []} = xref:q(S, "LM - X - U - B"),
+
+ ok.
+
+modules_mode_check(S, Info) ->
+ %% B subset of XU,
+ ?line {ok, []} = xref:q(S, "B - XU"),
+
+ %% M = AM + LM + UM
+ ?line {ok, M} = xref:q(S, "M"),
+ ?line {ok, M} = xref:q(S, "AM + LM + UM"),
+
+ %% DF is a subset of X U B, etc.
+ ?line {ok, []} = xref:q(S, "DF - X - B"),
+ ?line {ok, []} = xref:q(S, "DF_3 - DF"),
+ ?line {ok, []} = xref:q(S, "DF_2 - DF_3"),
+ ?line {ok, []} = xref:q(S, "DF_1 - DF_2"),
+
+ %% AM, LM and UM are disjoint.
+ ?line {ok, []} = xref:q(S, "AM * LM + AM * UM + LM * UM"),
+
+ %% (App) M subset of A
+ ?line {ok, []} = xref:q(S, "(App) M - A"),
+
+ ?line AM = info(Info, no_analyzed_modules),
+ ?line {ok, AM} = xref:q(S, "# AM"),
+
+ ?line A = info(Info, no_applications),
+ ?line {ok, A} = xref:q(S, "# A"),
+
+ ?line NoR = info(Info, no_releases),
+ ?line {ok, NoR} = xref:q(S, "# R"),
+
+ ok.
+
+%% Checks the counters of some of the overall and modules info functions.
+%% (Applications and releases are not checked.)
+check_count(S) ->
+ %%{ok, R} = xref:q(S, 'R'),
+ %% {ok, A} = xref:q(S, 'A'),
+ {ok, M} = xref:q(S, 'AM'),
+
+ {ok, _} = xref:q(S,
+ "Extra := _:module_info/\"(0|1)\" + LM"),
+
+ %% info/1:
+ {ok, NoR} = xref:q(S, '# R'),
+ {ok, NoA} = xref:q(S, '# A'),
+ {ok, NoM} = xref:q(S, '# AM'),
+ {ok, NoCalls} = xref:q(S, '# (XLin) E + # (LLin) E'),
+ {ok, NoFunCalls} = xref:q(S, '# E'),
+ {ok, NoXCalls} = xref:q(S, '# XC'),
+ {ok, NoLCalls} = xref:q(S, '# LC'),
+ {ok, NoLXCalls} = xref:q(S, '# (XC * LC)'),
+ NoAllCalls = NoXCalls + NoLCalls,
+ {ok, NoFun} = xref:q(S, '# (F - Extra)'),
+ {ok, NoICalls} = xref:q(S, '# EE'),
+
+ Info = xref:info(S),
+ NoR = info(Info, no_releases),
+ NoA = info(Info, no_applications),
+ NoM = info(Info, no_analyzed_modules),
+ {NoResolved, NoUC} = info(Info, no_calls),
+ NoCalls = NoResolved + NoUC,
+ {NoLocal, NoExternal, NoUnres} = info(Info, no_function_calls),
+ NoAllCalls = NoLocal + NoExternal + NoUnres,
+ NoAllCalls = NoFunCalls + NoLXCalls,
+ {NoLocalFuns, NoExportedFuns} = info(Info, no_functions),
+ NoFun = NoLocalFuns + NoExportedFuns,
+ NoICalls = info(Info, no_inter_function_calls),
+
+ %% per module
+ info_module(M, S),
+
+ ok.
+
+info_module([M | Ms], S) ->
+ {ok, NoCalls} = per_module("T = (E | ~p : Mod), # (XLin) T + # (LLin) T",
+ M, S),
+ {ok, NoFunCalls} = per_module("# (E | ~p : Mod)", M, S),
+ {ok, NoXCalls} = per_module("# (XC | ~p : Mod)", M, S),
+ {ok, NoLCalls} = per_module("# (LC | ~p : Mod)", M, S),
+ {ok, NoLXCalls} = per_module("# ((XC * LC) | ~p : Mod)", M, S),
+ NoAllCalls = NoXCalls + NoLCalls,
+ {ok, NoFun} = per_module("# (F * ~p : Mod - Extra)", M, S),
+ {ok, NoICalls} = per_module("# (EE | ~p : Mod)", M, S),
+
+ [{_M,Info}] = xref:info(S, modules, M),
+ {NoResolved, NoUC} = info(Info, no_calls),
+ NoCalls = NoResolved + NoUC,
+ {NoLocal, NoExternal, NoUnres} = info(Info, no_function_calls),
+ NoAllCalls = NoLocal + NoExternal + NoUnres,
+ NoAllCalls = NoFunCalls + NoLXCalls,
+ {NoLocalFuns, NoExportedFuns} = info(Info, no_functions),
+ NoFun = NoLocalFuns + NoExportedFuns,
+ NoICalls = info(Info, no_inter_function_calls),
+
+ info_module(Ms, S);
+info_module([], _S) ->
+ ok.
+
+per_module(Q, M, S) ->
+ xref:q(S, f(Q, [M])).
+
+info(Info, What) ->
+ {value, {What, Value}} = lists:keysearch(What, 1, Info),
+ Value.
+
+f(S, A) ->
+ flatten(io_lib:format(S, A)).
+
+fatom(R) ->
+ list_to_atom(flatten(xref:format_error(R))).
+
+start(Server) ->
+ ?line case xref:start(Server) of
+ {error, {already_started, _Pid}} ->
+ ?line xref:stop(Server),
+ ?line xref:start(Server);
+ R -> R
+ end.
+
+add_erts_code_path(KernelPath) ->
+ VersionDirs =
+ filelib:is_dir(
+ filename:join(
+ [code:lib_dir(),
+ lists:flatten(
+ ["kernel-",
+ [X ||
+ {kernel,_,X} <-
+ application_controller:which_applications()]])])),
+ case VersionDirs of
+ true ->
+ case code:lib_dir(erts) of
+ String when is_list(String) ->
+ [KernelPath, fname(String,"ebin")];
+ _Other1 ->
+ [KernelPath]
+ end;
+ false ->
+ % Clearcase?
+ PrelPath = filename:join([code:lib_dir(),"..","erts","preloaded"]),
+ case filelib:is_dir(PrelPath) of
+ true ->
+ [KernelPath, fname(PrelPath,"ebin")];
+ false ->
+ [KernelPath]
+ end
+ end.
+
+
diff --git a/lib/tools/test/xref_SUITE_data/depr_r9c.beam b/lib/tools/test/xref_SUITE_data/depr_r9c.beam
new file mode 100644
index 0000000000..82f0eef5a1
--- /dev/null
+++ b/lib/tools/test/xref_SUITE_data/depr_r9c.beam
Binary files differ
diff --git a/lib/tools/test/xref_SUITE_data/dir/dir/dummy b/lib/tools/test/xref_SUITE_data/dir/dir/dummy
new file mode 100644
index 0000000000..7f62a93220
--- /dev/null
+++ b/lib/tools/test/xref_SUITE_data/dir/dir/dummy
@@ -0,0 +1,2 @@
+This directory is not empty.
+Prevents winzip from removing the directory.
diff --git a/lib/tools/test/xref_SUITE_data/dir/jam/x.jam b/lib/tools/test/xref_SUITE_data/dir/jam/x.jam
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/lib/tools/test/xref_SUITE_data/dir/jam/x.jam
diff --git a/lib/tools/test/xref_SUITE_data/lib_test/cp.erl b/lib/tools/test/xref_SUITE_data/lib_test/cp.erl
new file mode 100644
index 0000000000..efe5d77854
--- /dev/null
+++ b/lib/tools/test/xref_SUITE_data/lib_test/cp.erl
@@ -0,0 +1,6 @@
+-module(cp).
+
+-export([t/0]).
+
+t() ->
+ lists:sort(a).
diff --git a/lib/tools/test/xref_SUITE_data/lib_test/lib1.erl b/lib/tools/test/xref_SUITE_data/lib_test/lib1.erl
new file mode 100644
index 0000000000..7d50fcb1af
--- /dev/null
+++ b/lib/tools/test/xref_SUITE_data/lib_test/lib1.erl
@@ -0,0 +1,6 @@
+-module(lib1).
+
+-export([f/0]).
+
+f() ->
+ true.
diff --git a/lib/tools/test/xref_SUITE_data/lib_test/lib2.erl b/lib/tools/test/xref_SUITE_data/lib_test/lib2.erl
new file mode 100644
index 0000000000..ca505dfd4f
--- /dev/null
+++ b/lib/tools/test/xref_SUITE_data/lib_test/lib2.erl
@@ -0,0 +1,14 @@
+-module(lib2).
+
+-export([f/0, g/0]).
+
+-deprecated({f,0,next_major_release}).
+
+f() ->
+ local().
+
+g() ->
+ true.
+
+local() ->
+ true.
diff --git a/lib/tools/test/xref_SUITE_data/lib_test/lib3.erl b/lib/tools/test/xref_SUITE_data/lib_test/lib3.erl
new file mode 100644
index 0000000000..8a900f8040
--- /dev/null
+++ b/lib/tools/test/xref_SUITE_data/lib_test/lib3.erl
@@ -0,0 +1,11 @@
+-module(lib3).
+
+-export([f/0, g/0]).
+
+-deprecated(module).
+
+f() ->
+ true.
+
+g() ->
+ true.
diff --git a/lib/tools/test/xref_SUITE_data/lib_test/t.erl b/lib/tools/test/xref_SUITE_data/lib_test/t.erl
new file mode 100644
index 0000000000..d2bd81110e
--- /dev/null
+++ b/lib/tools/test/xref_SUITE_data/lib_test/t.erl
@@ -0,0 +1,14 @@
+-module(t).
+
+-export([t/0]).
+
+t() ->
+ %% lib1: only unknown functions used
+ %% lib2: one known used, one unknown function used, one local used
+ %% lib3: one known function used
+ lib1:unknown(),
+ lib2:f(), %% known, g/0 not used
+ lib2:unknown(),
+ lib2:local(),
+ lib3:f(),
+ unknown:unknown().
diff --git a/lib/tools/test/xref_SUITE_data/md/x__x.erl b/lib/tools/test/xref_SUITE_data/md/x__x.erl
new file mode 100644
index 0000000000..83234e1d3f
--- /dev/null
+++ b/lib/tools/test/xref_SUITE_data/md/x__x.erl
@@ -0,0 +1,7 @@
+-module(x__x).
+
+-export([t/1]).
+
+t(A) ->
+ y__y:t(A),
+ y__y:t(A,A).
diff --git a/lib/tools/test/xref_SUITE_data/md/y__y.erl b/lib/tools/test/xref_SUITE_data/md/y__y.erl
new file mode 100644
index 0000000000..b2bb783fd8
--- /dev/null
+++ b/lib/tools/test/xref_SUITE_data/md/y__y.erl
@@ -0,0 +1,12 @@
+-module(y__y).
+
+-export([t/1]).
+
+t(A) ->
+ x__x:t(A).
+
+l() ->
+ l1().
+
+l1() ->
+ l().
diff --git a/lib/tools/test/xref_SUITE_data/read/read.beam.v1 b/lib/tools/test/xref_SUITE_data/read/read.beam.v1
new file mode 100644
index 0000000000..3b8e9c4c38
--- /dev/null
+++ b/lib/tools/test/xref_SUITE_data/read/read.beam.v1
Binary files differ
diff --git a/lib/tools/test/xref_SUITE_data/read/read.erl b/lib/tools/test/xref_SUITE_data/read/read.erl
new file mode 100644
index 0000000000..4a0cc280c3
--- /dev/null
+++ b/lib/tools/test/xref_SUITE_data/read/read.erl
@@ -0,0 +1,175 @@
+-module(read).
+
+-export([lc/0, funfuns/0, bi/0]).
+
+-xref({xx,ff,22}).
+-xref({{all,0},{no,0}}).
+-xref([{{all,0},{i,0}},{{all,0},{x2,5}}]).
+-xref([apa]).
+-xref({all,0}).
+-xref([{{{all},0},{no,0}},{{all,0},{m,x2,5}}]).
+-xref([{{a,14},{q,f,17}}]).
+-xref({{i,f,17},{g,18}}).
+-xref({{j,f,17},{i,g,18}}).
+
+-xref({{funfuns,0},{'$F_EXPR',177}}).
+-xref({{funfuns,0},{?MODULE,'$F_EXPR',178}}).
+-xref({{funfuns,0},{modul,'$F_EXPR',179}}).
+
+lc() ->
+ Tab = ets:new(),
+ [Mt||{_M,Mt} <- ets:tab2list(Tab)].
+
+funfuns() ->
+ A = variable,
+
+ %% Spawn...
+
+ %% Recognized. POS1=28.
+ spawn(fun() -> mod17:fun17() end),
+ spawn(fun local/0),
+ spawn(fun binary_to_term/1), % builtin, not collected
+ spawn({dist,func}),
+ spawn({dist,A}), % {dist,'$F_EXPR',0}
+ spawn_link(fun() -> mod17:fun17() end),
+ spawn_link({dist,func}),
+ spawn_link({dist,A}), % {dist,'$F_EXPR',0}
+
+ %% POS2=POS1+10
+ spawn({dist,func}(arg1,arg2), {d,f}),
+ spawn({dist,func}(arg1,arg2), fun() -> mod42:func() end),
+ spawn_link({dist,func}(arg1,arg2), {d,f}),
+ spawn_link({dist,func}(arg1,arg2), fun() -> mod42:func() end),
+
+ %% POS3=POS2+6
+ spawn(dist, func, [arg1,arg2]), % spawn/3 is builtin
+ spawn(expr, A, [arg1]), % {expr,'$F_EXPR',1}
+ spawn_link(dist, func, [arg1,arg2]), % spawn_link/3 is builtin
+ spawn_link(expr, A, [arg1,arg2]), % {expr,'$F_EXPR',2}
+
+ %% POS4=POS3+6
+ spawn(node, modul, function, []),
+ spawn(node, modul, A, [a]), % {modul,'$F_EXPR',1}
+ spawn({dist,func}(arg1,arg2), spm, spf, [a,b]),
+ spawn({dist,func}(arg1,arg2), spm, A, [a]), % {spm,'$F_EXPR',1}
+ spawn_link({dist,func}(arg1,arg2), spm, spf, [a,b]),
+ spawn_link({dist,func}(arg1,arg2), spm, A, [a]), % {spm,'$F_EXPR',1}
+ spawn_opt(spm, spf, [arg3, arg4], [opt1, bi()]),
+ spawn_opt(spm, A, [a], [opt1, bi()]), % {spm,'$F_EXPR',1}
+
+ %% Not recognized or invalid. POS5=POS4+10
+ spawn(A), % {'$M_EXPR','$F_EXPR',0}
+ spawn(17), % {'$M_EXPR','$F_EXPR',0}
+ spawn_link(A), % {'$M_EXPR','$F_EXPR',0}
+
+ %% POS6=POS5+5
+ spawn({a,b},[1008]), % {'$M_EXPR','$F_EXPR',0}
+ spawn_link({a,b},[1008]), % {'$M_EXPR','$F_EXPR',0}
+
+ spawn(n, A, A), % {n,'$F_EXPR',-1}
+
+ %% POS7=POS6+6
+ spawn(n, A,f,[1007]), % {'$M_EXPR',f,1}, spawn/3 is builtin
+ spawn_opt(A,f,[1007],[]), % {'$M_EXPR',f,1}
+
+ %% Apply...
+
+ %% Recognized. POS8=POS7+6
+ {hej,san}(1002),
+ {hej,A}(1002), % {hej,'$F_EXPR',1}
+ t:A(1003), % {t,'$F_EXPR',1}
+ apply({a,b},[1005]),
+ apply({a,A},[1005]), % {a,'$F_EXPR',1}
+ apply(m,f,[100011]),
+ apply(m,A,[100011]), % {m,'$F_EXPR',1}
+ %% POS9=POS8+8
+ apply(A, f, [bi()]), % {'$M_EXPR',f,1}
+ {erlang,apply}({a,b},[8888]),
+ {erlang,apply}({a,A},[8888]), % {a,'$F_EXPR',1}
+ {erlang,apply}({erlang,apply},[{erlang,not_a_function},[7777]]),
+ apply(erlang, apply, [erlang, apply, [mod, func, [atom77,tjohej]]]),
+ erlang:apply(foo), % not an apply, but an unknown function
+ apply(fun(X) -> math:add3(X) end, [7]),
+ (fun(X) -> q:f(X) end)(3008),
+
+ %% Not recognized or invalid. POS10=POS9+10
+ A:foo(1000), % {'$M_EXPR',foo,1}
+ A(17), % {'$M_EXPR','$F_EXPR',1}
+ A(17,[one,two]), % {'$M_EXPR','$F_EXPR',2}
+ apply(apa,[1001]), % {'$M_EXPR','$F_EXPR',1}
+ {mod1:fun1(hej),san}(1017), % {'$M_EXPR',san,1}
+ A:A(1004), % {'$M_EXPR','$F_EXPR',1}
+ %% POS11=POS10+7
+ apply(A,A,[1006]), % {'$M_EXPR','$F_EXPR',1}
+ apply(A,A,[1009 | 10010]), % {'$M_EXPR','$F_EXPR',-1}
+ apply(m,f,[10000 | 9999]), % {m,f,-1}
+ apply(m,f,a), % {m,f,-1}
+ 3(a), % {'$M_EXPR','$F_EXPR',1}
+ apply(3,[a]), % {'$M_EXPR','$F_EXPR',1}
+
+ %% POS12=POS11+8
+ apply(A, A), % number of arguments is not known, {'$M_EXPR','$F_EXPR',-1}
+ Args0 = [list],
+ Args = [a | Args0], % number of arguments is known
+ apply(A, Args), % {'$M_EXPR','$F_EXPR',2}
+ apply(m3, f3, Args), %
+ NotArgs = [is_not, a | list], % number of arguments is not known
+ apply(A, NotArgs), % {'$M_EXPR','$F_EXPR',-1}
+ apply(m4, f4, NotArgs), % {m4,f4,-1}
+
+ %% OTP Internal. POS13=POS12+10
+ erts_debug:apply(dm, df, [17], foobar),
+ erts_debug:apply(debug, A, [], A), % {debug,'$F_EXPR',0}
+ erts_debug:apply(A, A, A, A). % {'$M_EXPR','$F_EXPR',-1}
+
+bi() when length([]) > 17 ->
+ foo:module_info(),
+ module_info(),
+ A = tjo,
+ t:foo(A),
+ case true of
+ true when integer(1) ->
+ X = foo;
+ false ->
+ X = flopp
+ end,
+ X == A;
+bi() ->
+ %% POS14=POS13+18
+ Z = fun(Y) -> Y end,
+ case true of
+ true when length([a,b]) > 4 ->
+ F = fun(X) -> X end,
+ F(3);
+ false ->
+ F = 17,
+ F(3) % {'$M_EXPR','$F_EXPR',1}
+ end,
+ Z(apa),
+ erlang:module_info();
+bi() ->
+ Bin11 = <<1, 17, 42>>,
+ Bin12 = <<"abc">>,
+ false = (Bin11 == Bin12),
+ A = 1, B = 17,
+ Bin3 = <<A, B, (bi()):16>>,
+ <<D:16, E, F/binary>> = Bin3,
+ X = 9, <<(X+1):8>>,
+ _Fyy = <<X:4/little-signed-integer-unit:8>>,
+ D + E + F.
+%bi() ->
+% %% POS15=POS14+13
+% try
+% foo:t(),
+% bar:t()
+% of
+% {v,1} ->
+% local();
+% {v,2} ->
+% foo:t()
+% catch
+% {'EXIT',_} -> bar:t()
+% end.
+
+local() ->
+ true.
diff --git a/lib/tools/test/xref_SUITE_data/rel2/lib/app1-1.0/ebin/dummy b/lib/tools/test/xref_SUITE_data/rel2/lib/app1-1.0/ebin/dummy
new file mode 100644
index 0000000000..7f62a93220
--- /dev/null
+++ b/lib/tools/test/xref_SUITE_data/rel2/lib/app1-1.0/ebin/dummy
@@ -0,0 +1,2 @@
+This directory is not empty.
+Prevents winzip from removing the directory.
diff --git a/lib/tools/test/xref_SUITE_data/rel2/lib/app1-1.1/ebin/dummy b/lib/tools/test/xref_SUITE_data/rel2/lib/app1-1.1/ebin/dummy
new file mode 100644
index 0000000000..7f62a93220
--- /dev/null
+++ b/lib/tools/test/xref_SUITE_data/rel2/lib/app1-1.1/ebin/dummy
@@ -0,0 +1,2 @@
+This directory is not empty.
+Prevents winzip from removing the directory.
diff --git a/lib/tools/test/xref_SUITE_data/rel2/lib/app2-1.1/ebin/dummy b/lib/tools/test/xref_SUITE_data/rel2/lib/app2-1.1/ebin/dummy
new file mode 100644
index 0000000000..7f62a93220
--- /dev/null
+++ b/lib/tools/test/xref_SUITE_data/rel2/lib/app2-1.1/ebin/dummy
@@ -0,0 +1,2 @@
+This directory is not empty.
+Prevents winzip from removing the directory.
diff --git a/lib/tools/test/xref_SUITE_data/rel2/x.erl b/lib/tools/test/xref_SUITE_data/rel2/x.erl
new file mode 100644
index 0000000000..2125760776
--- /dev/null
+++ b/lib/tools/test/xref_SUITE_data/rel2/x.erl
@@ -0,0 +1,16 @@
+-module(x).
+
+-export([t/0, xx/0]).
+-deprecated({t,0,eventually}).
+
+t() ->
+ true.
+
+xx() ->
+ x:undef().
+
+l() ->
+ l1().
+
+l1() ->
+ l().
diff --git a/lib/tools/test/xref_SUITE_data/rel2/y.erl b/lib/tools/test/xref_SUITE_data/rel2/y.erl
new file mode 100644
index 0000000000..2a6e65e78f
--- /dev/null
+++ b/lib/tools/test/xref_SUITE_data/rel2/y.erl
@@ -0,0 +1,6 @@
+-module(y).
+
+-export([t/0]).
+
+t() ->
+ x:t().
diff --git a/lib/tools/test/xref_SUITE_data/update/x.erl.1 b/lib/tools/test/xref_SUITE_data/update/x.erl.1
new file mode 100644
index 0000000000..6af13329a2
--- /dev/null
+++ b/lib/tools/test/xref_SUITE_data/update/x.erl.1
@@ -0,0 +1,6 @@
+-module(x).
+
+-export([t/0]).
+
+t() ->
+ erlang:atom_to_list(tjohoo).
diff --git a/lib/tools/test/xref_SUITE_data/update/x.erl.2 b/lib/tools/test/xref_SUITE_data/update/x.erl.2
new file mode 100644
index 0000000000..e303a758d2
--- /dev/null
+++ b/lib/tools/test/xref_SUITE_data/update/x.erl.2
@@ -0,0 +1,6 @@
+-module(x).
+
+-export([t/0]).
+
+t() ->
+ erlang:list_to_atom("tjohoo").
diff --git a/lib/tools/vsn.mk b/lib/tools/vsn.mk
index 644e8b719b..13cf5af9f5 100644
--- a/lib/tools/vsn.mk
+++ b/lib/tools/vsn.mk
@@ -16,4 +16,4 @@
#
# %CopyrightEnd%
-TOOLS_VSN = 2.6.5
+TOOLS_VSN = 2.6.5.1