diff options
Diffstat (limited to 'lib/tools')
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 Binary files differnew file mode 100644 index 0000000000..82f0eef5a1 --- /dev/null +++ b/lib/tools/test/xref_SUITE_data/depr_r9c.beam 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 Binary files differnew file mode 100644 index 0000000000..3b8e9c4c38 --- /dev/null +++ b/lib/tools/test/xref_SUITE_data/read/read.beam.v1 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 |